aboutsummaryrefslogtreecommitdiff
path: root/loader/src/main/java/kr
diff options
context:
space:
mode:
Diffstat (limited to 'loader/src/main/java/kr')
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DGInterface.java31
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DungeonsGuideReloadListener.java24
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java261
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/Authenticator.java257
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/TokenStatus.java27
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/ModDownloader.java302
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/Update.java46
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/UpdateBranch.java29
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthServerException.java34
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/NoSuitableLoaderFoundException.java32
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/PrivacyPolicyRequiredException.java22
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/ReferenceLeakedException.java22
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/TokenExpiredException.java26
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiLoadingError.java141
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiPrivacyPolicy.java53
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/IDGLoader.java39
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java134
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java64
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGConnection.java47
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandler.java36
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandlerFactory.java37
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/util/QRCodeGenerator.java43
22 files changed, 1707 insertions, 0 deletions
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DGInterface.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DGInterface.java
new file mode 100755
index 00000000..c88a3cbf
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DGInterface.java
@@ -0,0 +1,31 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2021 cyoung06
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher;
+
+import net.minecraft.client.resources.IResourceManager;
+import net.minecraftforge.fml.common.event.FMLInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
+
+import java.io.File;
+
+public interface DGInterface {
+ void init(File resourceDir);
+ void unload();
+ void onResourceReload(IResourceManager a);
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DungeonsGuideReloadListener.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DungeonsGuideReloadListener.java
new file mode 100644
index 00000000..7252a9db
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DungeonsGuideReloadListener.java
@@ -0,0 +1,24 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2021 cyoung06
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher;
+
+public interface DungeonsGuideReloadListener {
+ public void unloadReference();
+ public void onLoad(DGInterface dgInterface);
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java
new file mode 100755
index 00000000..7cc0f806
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java
@@ -0,0 +1,261 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2021 cyoung06
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher;
+
+import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
+import com.mojang.authlib.exceptions.InvalidCredentialsException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.AuthServerException;
+import kr.syeyoung.dungeonsguide.launcher.authentication.Authenticator;
+import kr.syeyoung.dungeonsguide.launcher.branch.ModDownloader;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.NoSuitableLoaderFoundException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.PrivacyPolicyRequiredException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.ReferenceLeakedException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.TokenExpiredException;
+import kr.syeyoung.dungeonsguide.launcher.gui.GuiLoadingError;
+import kr.syeyoung.dungeonsguide.launcher.gui.GuiPrivacyPolicy;
+import kr.syeyoung.dungeonsguide.launcher.loader.IDGLoader;
+import kr.syeyoung.dungeonsguide.launcher.loader.JarLoader;
+import kr.syeyoung.dungeonsguide.launcher.loader.LocalLoader;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiMainMenu;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.resources.IReloadableResourceManager;
+import net.minecraftforge.client.event.GuiOpenEvent;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.common.config.Configuration;
+import net.minecraftforge.fml.common.Mod;
+import net.minecraftforge.fml.common.Mod.EventHandler;
+import net.minecraftforge.fml.common.ProgressManager;
+import net.minecraftforge.fml.common.event.FMLInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Mod(modid = Main.MOD_ID, version = Main.VERSION)
+public class Main
+{
+ public static final String MOD_ID = "dungeons_guide_wrapper";
+ public static final String VERSION = "1.0";
+ public static final String DOMAIN = "http://testmachine:8080/api";
+
+ private static Main main;
+
+ private File configDir;
+
+ private DGInterface dgInterface;
+ private Authenticator authenticator = new Authenticator();
+ private ModDownloader modDownloader = new ModDownloader(authenticator);
+
+ private List<DungeonsGuideReloadListener> listeners = new ArrayList<>();
+
+ public void addDGReloadListener(DungeonsGuideReloadListener dungeonsGuideReloadListener) {
+ listeners.add(Objects.requireNonNull(dungeonsGuideReloadListener));
+ }
+ public void removeDGReloadListener(DungeonsGuideReloadListener dungeonsGuideReloadListener) {
+ listeners.remove(dungeonsGuideReloadListener);
+ }
+
+ private IDGLoader currentLoader;
+
+ private Throwable lastError;
+ private boolean isMcLoaded;
+
+
+
+
+ @EventHandler
+ public void initEvent(FMLInitializationEvent initializationEvent)
+ {
+ MinecraftForge.EVENT_BUS.register(this);
+ if (dgInterface != null) {
+ try {
+ dgInterface.init(configDir);
+
+ for (DungeonsGuideReloadListener listener : listeners) {
+ listener.onLoad(dgInterface);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ setLastFatalError(e);
+ }
+ }
+ }
+
+ public void unload() throws ReferenceLeakedException {
+ if (currentLoader != null && !currentLoader.isUnloadable()) {
+ throw new UnsupportedOperationException("Current version is not unloadable");
+ }
+ dgInterface = null;
+ for (DungeonsGuideReloadListener listener : listeners) {
+ listener.unloadReference();
+ }
+ if (currentLoader != null) {
+ currentLoader.unloadJar();
+ }
+ currentLoader = null;
+ }
+ private void load(IDGLoader newLoader) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ partialLoad(newLoader);
+
+ dgInterface.init(configDir);
+
+ for (DungeonsGuideReloadListener listener : listeners) {
+ listener.onLoad(dgInterface);
+ }
+ }
+ private void partialLoad(IDGLoader newLoader) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ if (dgInterface != null) throw new IllegalStateException("DG is loaded");
+ newLoader.loadJar(authenticator);
+ dgInterface = newLoader.getInstance();
+ currentLoader = newLoader;
+ }
+
+ public void reload(IDGLoader newLoader) {
+ try {
+ unload();
+ load(newLoader);
+ } catch (Exception e) {
+ dgInterface = null;
+ currentLoader = null;
+
+ e.printStackTrace();
+ setLastFatalError(e);
+ }
+ }
+
+ public void tryOpenError() {
+ if (isMcLoaded) Minecraft.getMinecraft().displayGuiScreen(obtainErrorGUI());
+ }
+
+ public GuiScreen obtainErrorGUI() {
+ if (lastError instanceof PrivacyPolicyRequiredException) {
+ return new GuiPrivacyPolicy();
+ } else if (lastError instanceof TokenExpiredException) {
+
+ } else if (lastError instanceof NoSuitableLoaderFoundException) {
+
+ } else if (lastError instanceof ReferenceLeakedException) {
+
+ } else if (lastError instanceof AuthServerException) {
+
+ } else if (lastError instanceof InvalidCredentialsException) {
+
+ } else if (lastError instanceof AuthenticationUnavailableException) {
+
+ } else if (lastError != null){
+ return new GuiLoadingError(lastError, () -> {lastError = null;});
+ }
+ if (lastError != null)
+ lastError.printStackTrace();
+ // when gets called init and stuff remove thing
+ return null;
+ }
+
+ @SubscribeEvent(priority = EventPriority.LOWEST)
+ public void onGuiOpen(GuiOpenEvent guiOpenEvent) {
+ if (guiOpenEvent.gui instanceof GuiMainMenu) {
+ isMcLoaded = true;
+ }
+ if (lastError != null && guiOpenEvent.gui instanceof GuiMainMenu) {
+ GuiScreen gui = obtainErrorGUI();
+ if (gui != null)
+ guiOpenEvent.gui = gui;
+ }
+ }
+
+ public String getLoaderName(Configuration configuration) {
+ String loader = System.getProperty("dg.loader");
+ if (loader == null) {
+ loader = configuration.get("loader", "modsource", "auto").getString();
+ }
+ if (loader == null) loader = "auto";
+ return loader;
+ }
+
+
+ public IDGLoader obtainLoader(Configuration configuration) {
+ String loader = getLoaderName(configuration);
+
+ if ("local".equals(loader) ||
+ (loader.equals("auto") && this.getClass().getResourceAsStream("/kr/syeyoung/dungeonsguide/DungeonsGuide.class") == null)) {
+ return new LocalLoader();
+ } else if ("jar".equals(loader) ||
+ (loader.equals("auto") && this.getClass().getResourceAsStream("/mod.jar") == null)) {
+ return new JarLoader();
+ } else if (loader.equals("auto") ){
+ // remote load
+ throw new UnsupportedOperationException(""); // yet
+ } else {
+ throw new NoSuitableLoaderFoundException(System.getProperty("dg.loader"), configuration.get("loader", "modsource", "auto").getString());
+ }
+ }
+
+ @EventHandler
+ public void preInit(FMLPreInitializationEvent preInitializationEvent) {
+ // setup static variables
+ main = this;
+ configDir = preInitializationEvent.getModConfigurationDirectory();
+
+ // setup preinit progress bar for well, progress bar!
+ ProgressManager.ProgressBar bar = ProgressManager.push("DungeonsGuide", 2);
+ try {
+ // Try authenticate
+ bar.step("Authenticating...");
+ authenticator.repeatAuthenticate(5);
+
+
+ // If authentication succeeds, obtain loader and partially load dungeons guide
+ File f = new File(preInitializationEvent.getModConfigurationDirectory(), "loader.cfg");
+ Configuration configuration = new Configuration(f);
+
+ bar.step("Instantiating...");
+ partialLoad(obtainLoader(configuration));
+
+ // Save config because... well to generate it
+ configuration.save();
+ } catch (Throwable t) {
+ dgInterface = null;
+ currentLoader = null;
+
+ t.printStackTrace();
+ setLastFatalError(t);
+ } finally {
+ while(bar.getStep() < bar.getSteps()) bar.step("");
+ ProgressManager.pop(bar);
+ }
+
+ ((IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager()).registerReloadListener(a -> {
+ if (dgInterface != null) dgInterface.onResourceReload(a);
+ });
+ }
+
+ public void setLastFatalError(Throwable t) {
+ lastError = t;
+ tryOpenError();
+ }
+
+ public static Main getMain() {
+ return main;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/Authenticator.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/Authenticator.java
new file mode 100755
index 00000000..ac30c5e3
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/Authenticator.java
@@ -0,0 +1,257 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2021 cyoung06
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.authentication;
+
+import com.mojang.authlib.exceptions.AuthenticationException;
+import com.mojang.authlib.minecraft.MinecraftSessionService;
+import kr.syeyoung.dungeonsguide.launcher.Main;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.AuthServerException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.PrivacyPolicyRequiredException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.TokenExpiredException;
+import lombok.Getter;
+import net.minecraft.client.Minecraft;
+import net.minecraft.util.Session;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.IOUtils;
+import org.json.JSONObject;
+import sun.reflect.Reflection;
+
+import javax.crypto.*;
+import java.io.*;
+import java.math.BigInteger;
+import java.net.*;
+import java.security.*;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.time.Instant;
+import java.util.UUID;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class Authenticator {
+ private String dgAccessToken;
+ @Getter
+ private TokenStatus tokenStatus = TokenStatus.UNAUTHENTICATED;
+
+ private final SecureRandom secureRandom = new SecureRandom();
+
+ private Lock authenticationLock = new ReentrantLock();
+
+ static {
+ Reflection.registerFieldsToFilter(Authenticator.class, "dgAccessToken"); // Please do not touch this field. I know there is a way to block it completely, but I won't do it here.
+ }
+
+ public String getRawToken() {
+ return dgAccessToken;
+ }
+ public String getUnexpiredToken() { // THIS METHOD MAY BLOCK.
+ if (tokenStatus != TokenStatus.AUTHENTICATED) throw new IllegalStateException("Token is not available");
+ long expiry = getJwtPayload(dgAccessToken).getLong("exp");
+ if (System.currentTimeMillis() >= expiry-2000 || tokenStatus == TokenStatus.EXPIRED) {
+ tokenStatus = TokenStatus.EXPIRED;
+ try {
+ repeatAuthenticate(5);
+ } catch (Throwable t) {
+ Main.getMain().setLastFatalError(t);
+ throw new TokenExpiredException(t);
+ }
+ }
+ return dgAccessToken;
+ }
+
+
+ private byte[] generateSharedSecret() {
+ byte[] bts = new byte[32];
+ secureRandom.nextBytes(bts);
+ return bts;
+ }
+
+ public String repeatAuthenticate(int tries) {
+ int cnt = 0;
+ while(true) {
+ try {
+ reauthenticate();
+ break;
+ } catch (IOException | AuthenticationException | NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ if (cnt == tries) throw new RuntimeException(e);
+ try {
+ Thread.sleep((long) Math.max(Math.pow(2, tries)* 100, 1000 * 10));
+ } catch (InterruptedException ex) {}
+ }
+ cnt++;
+ }
+ return dgAccessToken;
+ }
+ public String reauthenticate() throws IOException, AuthenticationException, NoSuchAlgorithmException {
+ try {
+ authenticationLock.lock();
+
+ tokenStatus = TokenStatus.UNAUTHENTICATED;
+ dgAccessToken = null;
+
+ MinecraftSessionService yggdrasilMinecraftSessionService = Minecraft.getMinecraft().getSessionService();
+
+ Session SECURE_USER_SESSION = Minecraft.getMinecraft().getSession();
+ dgAccessToken = requestAuth(SECURE_USER_SESSION.getProfile().getId(), SECURE_USER_SESSION.getProfile().getName()); // id: uuid, name: username
+
+ JSONObject d = getJwtPayload(dgAccessToken);
+ byte[] sharedSecret = generateSharedSecret(); // Notice.... shared secret is generated on the client side unlike dg 3.0. Yep, I was a stupid when making 3.0.
+
+ String hash = calculateServerHash(sharedSecret, Base64.decodeBase64(d.getString("publicKey"))); // Public Key here is server's public key.
+
+ byte[] encodedSharedSecret;
+ try {
+ Cipher cipher = Cipher.getInstance("RSA");
+ cipher.init(Cipher.ENCRYPT_MODE, KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(d.getString("publicKey")))));
+ encodedSharedSecret = cipher.doFinal(sharedSecret);
+ } catch (NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException |
+ InvalidKeySpecException |
+ InvalidKeyException e) {
+ throw new RuntimeException(e);
+ } // Server connection is SSL but I still encrypt it using publicKey. Additional layer of security considering the request goes through cloudflare. (it's not like I don't trust cloudflare, but idk)
+
+ yggdrasilMinecraftSessionService.joinServer(SECURE_USER_SESSION.getProfile(), SECURE_USER_SESSION.getToken(), hash); // Sent to "MOJANG" Server.
+
+ JSONObject furtherStuff = verifyAuth(dgAccessToken, encodedSharedSecret);
+
+ dgAccessToken = furtherStuff.getString("jwt");
+ if ("TOS_PRIVACY_POLICY_ACCEPT_REQUIRED".equals(furtherStuff.getString("result"))) {
+ tokenStatus = TokenStatus.PP_REQUIRED;
+ throw new PrivacyPolicyRequiredException();
+ }
+ tokenStatus = TokenStatus.AUTHENTICATED;
+ return this.dgAccessToken;
+ } finally {
+ authenticationLock.unlock();
+ }
+ }
+
+ public String acceptLatestTOS() throws IOException {
+ try {
+ authenticationLock.lock();
+ if (tokenStatus != TokenStatus.PP_REQUIRED) throw new IllegalStateException("Already accepted TOS");
+ JSONObject furtherStuff = acceptPrivacyPolicy(dgAccessToken);
+ dgAccessToken = furtherStuff.getString("jwt");
+ if ("TOS_PRIVACY_POLICY_ACCEPT_REQUIRED".equals(furtherStuff.getString("result"))) {
+ tokenStatus = TokenStatus.PP_REQUIRED;
+ throw new PrivacyPolicyRequiredException();
+ }
+ tokenStatus = TokenStatus.AUTHENTICATED;
+ return this.dgAccessToken;
+ } finally {
+ authenticationLock.unlock();
+ }
+ }
+
+ public JSONObject getJwtPayload(String jwt) {
+ String midPart = jwt.split("\\.")[1].replace("+", "-").replace("/", "_");
+ String base64Decode = new String(Base64.decodeBase64(midPart)); // padding
+ return new JSONObject(base64Decode);
+ }
+
+ private String requestAuth(UUID uuid, String nickname) throws IOException {
+ HttpURLConnection urlConnection = request("POST", "/auth/v2/requestAuth");
+ urlConnection.setRequestProperty("Content-Type", "application/json");
+
+ urlConnection.getOutputStream().write(("{\"uuid\":\""+uuid.toString()+"\",\"nickname\":\""+nickname+"\"}").getBytes());
+ try (InputStream is = obtainInputStream(urlConnection)) {
+ String payload = String.join("\n", IOUtils.readLines(is));
+ if (urlConnection.getResponseCode() != 200)
+ System.out.println("/auth/requestAuth :: Received " + urlConnection.getResponseCode() + " along with\n" + payload);
+
+ JSONObject json = new JSONObject(payload);
+
+ if ("Success".equals(json.getString("status"))) {
+ return json.getString("data");
+ } else {
+ throw new AuthServerException(json);
+ }
+ }
+ }
+ private JSONObject verifyAuth(String tempToken, byte[] encryptedSecret) throws IOException {
+ HttpURLConnection urlConnection = request("POST", "/auth/v2/authenticate");
+ urlConnection.setRequestProperty("Content-Type", "application/json");
+
+ urlConnection.getOutputStream().write(("{\"jwt\":\""+tempToken+"\",\"sharedSecret\":\""+Base64.encodeBase64String(encryptedSecret)+"\"}").getBytes());
+ try (InputStream is = obtainInputStream(urlConnection)) {
+ String payload = String.join("\n", IOUtils.readLines(is));
+ if (urlConnection.getResponseCode() != 200)
+ System.out.println("/auth/authenticate :: Received " + urlConnection.getResponseCode() + " along with\n" + payload);
+
+ JSONObject json = new JSONObject(payload);
+
+ if ("Success".equals(json.getString("status"))) {
+ return json.getJSONObject("data");
+ } else {
+ throw new AuthServerException(json);
+ }
+ }
+ }
+ private JSONObject acceptPrivacyPolicy(String tempToken) throws IOException {
+ HttpURLConnection urlConnection = request("POST", "/auth/v2/acceptPrivacyPolicy");
+
+ urlConnection.getOutputStream().write(tempToken.getBytes());
+ try (InputStream is = obtainInputStream(urlConnection)) {
+ String payload = String.join("\n", IOUtils.readLines(is));
+ if (urlConnection.getResponseCode() != 200)
+ System.out.println("/auth/authenticate :: Received " + urlConnection.getResponseCode() + " along with\n" + payload);
+
+ JSONObject json = new JSONObject(payload);
+
+ if ("Success".equals(json.getString("status"))) {
+ return json.getJSONObject("data");
+ } else {
+ throw new AuthServerException(json);
+ }
+ }
+ }
+
+
+ private String calculateServerHash(byte[] a, byte[] b) throws NoSuchAlgorithmException {
+ MessageDigest c = MessageDigest.getInstance("SHA-1");
+ c.update("".getBytes());
+ c.update(a);
+ c.update(b);
+ byte[] d = c.digest();
+ return new BigInteger(d).toString(16);
+ }
+
+ public InputStream obtainInputStream(HttpURLConnection huc) {
+ InputStream inputStream = null;
+ try {
+ inputStream = huc.getInputStream();
+ } catch (Exception e) {
+ inputStream = huc.getErrorStream();
+ }
+ return inputStream;
+ }
+
+ public HttpURLConnection request(String method, String url) throws IOException {
+ HttpURLConnection urlConnection = (HttpURLConnection) new URL(Main.DOMAIN+url).openConnection();
+ urlConnection.setRequestMethod(method); // TODO: setup SSL certificate here, because.... SOME PEOPLE HAVE THAT ISSUE, I HAVE NO IDEA WHY THEY DONT HAVE CLOUDFLARE CERTS INSTALLED ON THEM
+ urlConnection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ urlConnection.setDoInput(true);
+ urlConnection.setDoOutput(true);
+ urlConnection.setAllowUserInteraction(true);
+ if (tokenStatus == TokenStatus.AUTHENTICATED)
+ urlConnection.setRequestProperty("Authorization", "Bearer "+getUnexpiredToken());
+ return urlConnection;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/TokenStatus.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/TokenStatus.java
new file mode 100644
index 00000000..a83818b8
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/TokenStatus.java
@@ -0,0 +1,27 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2021 cyoung06
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.authentication;
+
+public enum TokenStatus {
+ UNAUTHENTICATED,
+ BANNED,
+ PP_REQUIRED,
+ AUTHENTICATED,
+ EXPIRED
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/ModDownloader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/ModDownloader.java
new file mode 100644
index 00000000..45eacee5
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/ModDownloader.java
@@ -0,0 +1,302 @@
+/*
+ * Dungeons Guide - The most intelligent Hypixel Skyblock Dungeons Mod
+ * Copyright (C) 2021 cyoung06
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package kr.syeyoung.dungeonsguide.launcher.branch;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import kr.syeyoung.dungeonsguide.launcher.authentication.Authenticator;
+import lombok.Getter;
+import net.minecraftforge.fml.common.ProgressManager;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.IOUtils;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import javax.crypto.*;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import javax.net.ssl.HttpsURLConnection;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class ModDownloader {
+ private Authenticator authenticator;
+
+ @Getter
+ private List<UpdateBranch> accessibleBranches = null;
+
+ public ModDownloader(Authenticator authenticator) {
+ this.au