aboutsummaryrefslogtreecommitdiff
path: root/loader/src
diff options
context:
space:
mode:
Diffstat (limited to 'loader/src')
-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
-rwxr-xr-xloader/src/main/resources/mcmod.info16
23 files changed, 1723 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.authenticator = authenticator;
+ }
+
+ public List<UpdateBranch> fetchAccessibleBranches() throws IOException {
+ HttpURLConnection urlConnection = authenticator.request("GET", "/updates/");
+ try (InputStream is = authenticator.obtainInputStream(urlConnection)) {
+ String payload = String.join("\n", IOUtils.readLines(is));
+
+ JSONArray jsonArray = new JSONArray(payload);
+ List<UpdateBranch> branches = new ArrayList<>();
+ for (Object o : jsonArray) {
+ if (o instanceof JSONObject) {
+ JSONObject branch = (JSONObject) o;
+ UpdateBranch updateBranch = new UpdateBranch();
+ updateBranch.setId(branch.getLong("id"));
+ updateBranch.setName(branch.getString("name"));
+ updateBranch.setMetadata(branch.getJSONObject("metadata").getJSONObject("metadataSchema"));
+ branches.add(updateBranch);
+ }
+ }
+ return this.accessibleBranches = branches;
+ }
+ }
+
+ public List<Update> fetchUpdates(Long branch, int page) throws IOException {
+ HttpURLConnection urlConnection = authenticator.request("GET", "/updates/"+branch+"/");
+ try (InputStream is = authenticator.obtainInputStream(urlConnection)) {
+ String payload = String.join("\n", IOUtils.readLines(is));
+
+ JSONArray jsonArray = new JSONArray(payload);
+ List<Update> updates = new ArrayList<>();
+ for (Object o : jsonArray) {
+ if (o instanceof JSONObject) {
+ JSONObject json = (JSONObject) o;
+ Update update = new Update();
+ update.setId(json.getLong("id"));
+ update.setBranchId(json.getLong("branchId"));
+ update.setName(json.getString("versionName"));
+ update.setUpdateLog(json.getString("updateLog"));
+ update.setMetadata(json.getJSONObject("metadata"));
+ for (Object assets : json.getJSONObject("assets").getJSONArray("assets")) {
+ if (assets instanceof JSONObject) {
+ JSONObject a_json = (JSONObject) assets;
+ Update.Asset asset = new Update.Asset();
+ asset.setName(a_json.getString("name"));
+ asset.setSize(a_json.getLong("size"));
+ asset.setObjectId(a_json.getString("objectId"));
+ asset.setAssetId(UUID.fromString(a_json.getString("assetID")));
+ update.getAssets().add(asset);
+ }
+ }
+ updates.add(update);
+ }
+ }
+ return updates;
+ }
+ }
+
+ /*
+ pls Close after done
+ */
+ public InputStream fetchAsset(Update update, UUID assetId) throws IOException {
+ HttpURLConnection urlConnection = authenticator.request("GET", "/updates/"+update.getBranchId()+"/"+update.getId()+"/"+assetId.toString());
+ try (InputStream is = authenticator.obtainInputStream(urlConnection)) {
+ String payload = String.join("\n", IOUtils.readLines(is));
+ JSONObject object = new JSONObject(payload);
+
+ HttpURLConnection connection = (HttpURLConnection) new URL(object.getString("url")).openConnection();
+ connection.setRequestMethod(object.getString("method"));
+ urlConnection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ urlConnection.setDoInput(true);
+ urlConnection.setDoOutput(true);
+ urlConnection.setAllowUserInteraction(true);
+ return authenticator.obtainInputStream(connection);
+ }
+ }
+
+
+
+
+
+
+ private ProgressManager.ProgressBar progressBar;
+
+ private PublicKey dgPublicKey;
+ private PublicKey getDGPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
+ if (dgPublicKey != null) return dgPublicKey;
+ X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decodeBase64("MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxO89qtwG67jNucQ9Y44c" +
+ "IUs/B+5BeJPs7G+RG2gfs4/2+tzF/c1FLDc33M7yKw8aKk99vsBUY9Oo8gxxiEPB" +
+ "JitP/qfon2THp94oM77ZTpHlmFoqbZMcKGZVI8yfvEL4laTM8Hw+qh5poQwtpEbK" +
+ "Xo47AkxygxJasUnykER2+aSTZ6kWU2D4xiNtFA6lzqN+/oA+NaYfPS0amAvyVlHR" +
+ "n/8IuGkxb5RrlqVssQstFnxsJuv88qdGSEqlcKq2tLeg9hb8eCnl2OFzvXmgbVER" +
+ "0JaV+4Z02fVG1IlR3Xo1mSit7yIU6++3usRCjx2yfXpnGGJUW5pe6YETjNew3ax+" +
+ "FAZ4GePWCdmS7FvBnbbABKo5pE06ZTfDUTCjQlAJQiUgoF6ntMJvQAXPu48Vr8q/" +
+ "mTcuZWVnI6CDgyE7nNq3WNoq3397sBzxRohMxuqzl3T19zkfPKF05iV2Ju1HQMW5" +
+ "I119bYrmVD240aGESZc20Sx/9g1BFpNzQbM5PGUlWJ0dhLjl2ge4ip2hHciY3OEY" +
+ "p2Qy2k+xEdenpKdL+WMRimCQoO9gWe2Tp4NmP5dppDXZgPjXqjZpnGs0Uxs+fXqW" +
+ "cwlg3MbX3rFl9so/fhVf4p9oXZK3ve7z5D6XSSDRYECvsKIa08WAxJ/U6n204E/4" +
+ "xUF+3ZgFPdzZGn2PU7SsnOsCAwEAAQ=="));
+ return dgPublicKey = KeyFactory.getInstance("RSA").generatePublic(spec);
+ }
+
+
+
+
+ private KeyPair rsaKey;
+ private KeyPair getKeyPair() {
+ KeyPairGenerator a = null;
+ try {
+ a = KeyPairGenerator.getInstance("RSA");
+ } catch (NoSuchAlgorithmException b) { }
+ a.initialize(1024);
+ this.rsaKey = a.generateKeyPair();
+ return this.rsaKey;
+ }
+
+
+ private final HashMap<String, byte[]> loadedResources = new HashMap<String, byte[]>();
+
+ public HashMap<String, byte[]> getResources() {
+ return loadedResources;
+ }
+
+ private void downloadSafe(String dgToken, String url, boolean isValidateSignature) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, CertificateException, KeyStoreException, KeyManagementException, SignatureException, InvalidKeySpecException {
+ HttpsURLConnection dgConnection = (HttpsURLConnection) new URL(url).openConnection();
+ dgConnection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ dgConnection.setRequestProperty("Content-Type", "application/json");
+ dgConnection.setRequestMethod("GET");
+ dgConnection.setRequestProperty("Authorization", dgToken);
+ dgConnection.setDoInput(true);
+ dgConnection.setDoOutput(true);
+
+ InputStream inputStream = dgConnection.getInputStream();
+ byte[] lengthBytes = new byte[4];
+ inputStream.read(lengthBytes);
+ int length = ((lengthBytes[0] & 0xFF) << 24) |
+ ((lengthBytes[1] & 0xFF) << 16) |
+ ((lengthBytes[2] & 0xFF) << 8) |
+ ((lengthBytes[3] & 0xFF));
+ while (inputStream.available() < length) ;
+ byte[] keyPayload = new byte[length];
+ inputStream.read(keyPayload);
+
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ cipher.init(Cipher.DECRYPT_MODE, this.rsaKey.getPrivate());
+ byte[] h = cipher.doFinal(keyPayload);
+
+ cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ SecretKeySpec keySpec = new SecretKeySpec(h, "AES");
+ IvParameterSpec ivSpec = new IvParameterSpec(h);
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
+ CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
+
+ cipherInputStream.read(lengthBytes);
+ length = ((lengthBytes[0] & 0xFF) << 24) |
+ ((lengthBytes[1] & 0xFF) << 16) |
+ ((lengthBytes[2] & 0xFF) << 8) |
+ ((lengthBytes[3] & 0xFF));
+
+ int totalLen = length;
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ byte[] buff = new byte[256];
+ while (totalLen > 0) {
+ int len = cipherInputStream.read(buff, 0, Math.min(buff.length, totalLen));
+ totalLen -= len;
+ bos.write(buff, 0, len);
+ }
+ byte[] body = bos.toByteArray();
+
+ byte[] signed = null;
+ if (isValidateSignature) {
+ progressBar.step("Validating Signature");
+ cipherInputStream.read(lengthBytes,0 , 4);
+ length = ((lengthBytes[0] & 0xFF) << 24) |
+ ((lengthBytes[1] & 0xFF) << 16) |
+ ((lengthBytes[2] & 0xFF) << 8) |
+ ((lengthBytes[3] & 0xFF));
+
+ totalLen = length;
+ bos = new ByteArrayOutputStream();
+ while (totalLen > 0) {
+ int len = cipherInputStream.read(buff, 0, Math.min(buff.length, totalLen));
+ totalLen -= len;
+ bos.write(buff, 0, len);
+ }
+ signed = bos.toByteArray();
+
+ Signature sign = Signature.getInstance("SHA512withRSA");
+ sign.initVerify(getDGPublicKey());
+ sign.update(body);
+ boolean truth = sign.verify(signed);
+ if (!truth) throw new SignatureException("DG SIGNATURE FORGED");
+ }
+
+ ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(body));
+ ZipEntry zipEntry;
+ while ((zipEntry=zipInputStream.getNextEntry()) != null) {
+ byte[] buffer = new byte[256];
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ int p = 0;
+ while((p = zipInputStream.read(buffer)) > 0) {
+ byteArrayOutputStream.write(buffer, 0, p);
+ }
+ this.loadedResources.put(zipEntry.getName(), byteArrayOutputStream.toByteArray());
+ }
+ dgConnection.disconnect();
+ }
+
+ public JsonElement getJsonSecured(String u) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, CertificateException, KeyStoreException, KeyManagementException {
+ HttpsURLConnection httpsURLConnection = (HttpsURLConnection) new URL(u).openConnection();
+ httpsURLConnection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ httpsURLConnection.setRequestProperty("Content-Type", "application/json");
+ httpsURLConnection.setRequestMethod("GET");
+ httpsURLConnection.setRequestProperty("Authorization", authenticator.getUnexpiredToken());
+ httpsURLConnection.setDoInput(true);
+ httpsURLConnection.setDoOutput(true);
+
+ InputStream inputStream = httpsURLConnection.getInputStream();
+ byte[] lengthPayload = new byte[4];
+ inputStream.read(lengthPayload);
+ int length = ((lengthPayload[0] & 0xFF) << 24) |
+ ((lengthPayload[1] & 0xFF) << 16) |
+ ((lengthPayload[2] & 0xFF) << 8) |
+ ((lengthPayload[3] & 0xFF));
+ while (inputStream.available() < length) ;
+ byte[] keyPayload = new byte[length];
+ inputStream.read(keyPayload);
+
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ cipher.init(Cipher.DECRYPT_MODE, this.rsaKey.getPrivate());
+ byte[] AESKey = cipher.doFinal(keyPayload);
+
+ cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ SecretKeySpec secretKeySpec = new SecretKeySpec(AESKey, "AES");
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(AESKey);
+ cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
+ CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
+ cipherInputStream.read(lengthPayload);
+ length = ((lengthPayload[0] & 0xFF) << 24) |
+ ((lengthPayload[1] & 0xFF) << 16) |
+ ((lengthPayload[2] & 0xFF) << 8) |
+ ((lengthPayload[3] & 0xFF));
+ JsonElement l = new JsonParser().parse(new InputStreamReader(cipherInputStream));
+ httpsURLConnection.disconnect();
+ return l;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/Update.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/Update.java
new file mode 100644
index 00000000..565ac5aa
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/Update.java
@@ -0,0 +1,46 @@
+/*
+ * 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 lombok.Data;
+import org.json.JSONObject;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+@Data
+public class Update {
+ private long id;
+ private long branchId;
+ private String name;
+ private String updateLog;
+ private JSONObject metadata;
+ private Instant releaseDate;
+ private List<Asset> assets = new ArrayList<>();
+
+ @Data
+ public static class Asset {
+ private String name;
+ private long size;
+ private String objectId;
+ private UUID assetId;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/UpdateBranch.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/UpdateBranch.java
new file mode 100644
index 00000000..f0b05ee4
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/UpdateBranch.java
@@ -0,0 +1,29 @@
+/*
+ * 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 lombok.Data;
+import org.json.JSONObject;
+
+@Data
+public class UpdateBranch {
+ private Long id;
+ private String name;
+ private JSONObject metadata;
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthServerException.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthServerException.java
new file mode 100644
index 00000000..be610c3f
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthServerException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.exceptions;
+
+import org.json.JSONObject;
+
+public class AuthServerException extends RuntimeException {
+ private JSONObject payload;
+
+ public AuthServerException(JSONObject payload) {
+ super(payload.getString("errorMessage"));
+ this.payload = payload;
+ }
+
+ public String getQRCode() {
+ return payload.getString("data");
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/NoSuitableLoaderFoundException.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/NoSuitableLoaderFoundException.java
new file mode 100644
index 00000000..c2f9a907
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/NoSuitableLoaderFoundException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.exceptions;
+
+import lombok.AllArgsConstructor;
+
+public class NoSuitableLoaderFoundException extends RuntimeException {
+ private String jvmFlag;
+ private String configuration;
+
+ public NoSuitableLoaderFoundException(String jvmFlag, String configuration) {
+ super("No suitable loader found: (flag: "+jvmFlag+" / config: "+configuration+")");
+ this.jvmFlag = jvmFlag;
+ this.configuration = configuration;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/PrivacyPolicyRequiredException.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/PrivacyPolicyRequiredException.java
new file mode 100644
index 00000000..006e03ca
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/PrivacyPolicyRequiredException.java
@@ -0,0 +1,22 @@
+/*
+ * 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.exceptions;
+
+public class PrivacyPolicyRequiredException extends RuntimeException{
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/ReferenceLeakedException.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/ReferenceLeakedException.java
new file mode 100644
index 00000000..3a88b862
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/ReferenceLeakedException.java
@@ -0,0 +1,22 @@
+/*
+ * 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.exceptions;
+
+public class ReferenceLeakedException extends Exception {
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/TokenExpiredException.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/TokenExpiredException.java
new file mode 100644
index 00000000..2c115e2f
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/TokenExpiredException.java
@@ -0,0 +1,26 @@
+/*
+ * 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.exceptions;
+
+public class TokenExpiredException extends RuntimeException{
+
+ public TokenExpiredException(Throwable parent) {
+ super(parent);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiLoadingError.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiLoadingError.java
new file mode 100644
index 00000000..f7cc5972
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiLoadingError.java
@@ -0,0 +1,141 @@
+/*
+ * 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.gui;
+
+import kr.syeyoung.dungeonsguide.launcher.util.QRCodeGenerator;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.*;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.texture.DynamicTexture;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.fml.common.FMLCommonHandler;
+import org.lwjgl.opengl.GL11;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Base64;
+import java.util.zip.GZIPOutputStream;
+
+public class GuiLoadingError extends GuiScreen {
+ private String stacktrace;
+ private Throwable throwable;
+
+ private DynamicTexture texture;
+ private ResourceLocation location;
+ private BufferedImage qrCode;
+ private Runnable clear;
+
+ public GuiLoadingError(Throwable t, Runnable clear) {
+ this.throwable = t;
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ stacktrace = sw.toString();
+
+
+ try {
+ qrCode = QRCodeGenerator.generateQRCode(stacktrace.getBytes());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ texture = new DynamicTexture(qrCode.getWidth(), qrCode.getHeight());
+ location = Minecraft.getMinecraft().getTextureManager().getDynamicTextureLocation("dg/errorqr", texture);
+
+ qrCode.getRGB(0,0,qrCode.getWidth(), qrCode.getHeight(), texture.getTextureData(), 0, qrCode.getWidth());
+
+ texture.updateDynamicTexture();
+ this.clear = clear;
+ }
+
+ @Override
+ public void initGui() {
+ ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft());
+ this.buttonList.add(new GuiButton(0, sr.getScaledWidth()/2-100,sr.getScaledHeight()-40 ,"Close Minecraft"));
+ this.buttonList.add(new GuiButton(1, sr.getScaledWidth()/2-100,sr.getScaledHeight()-70 ,"Play Without DG"));
+ clear.run();
+ }
+
+ @Override
+ protected void actionPerformed(GuiButton button) throws IOException {
+ super.actionPerformed(button);
+ if (button.id == 0) {
+ FMLCommonHandler.instance().exitJava(-1,true);
+ } else if (button.id == 1) {
+ Minecraft.getMinecraft().displayGuiScreen(null);
+ }
+ }
+
+ @Override
+ public void drawScreen(int mouseX, int mouseY, float partialTicks) {
+ super.drawBackground(1);
+
+ ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft());
+ FontRenderer fontRenderer = Minecraft.getMinecraft().fontRendererObj;
+ fontRenderer.drawString("DungeonsGuide has ran into unknown error while loading itself", (sr.getScaledWidth()-fontRenderer.getStringWidth("DungeonsGuide has ran into unknown error while loading itself"))/2,40,0xFFFF0000);
+ fontRenderer.drawString("Please contact DungeonsGuide support with this screen", (sr.getScaledWidth()-fontRenderer.getStringWidth("Please contact DungeonsGuide support with this screen"))/2, (int) (40+fontRenderer.FONT_HEIGHT*1.5),0xFFFF0000);
+
+ int tenth = sr.getScaledWidth() / 10;
+
+ Gui.drawRect(tenth, 70,sr.getScaledWidth()-tenth, sr.getScaledHeight()-80, 0xFF5B5B5B);
+ String[] split = stacktrace.split("\n");
+ clip(sr, tenth, 70,sr.getScaledWidth()-2*tenth, sr.getScaledHeight()-150);
+ GL11.glEnable(GL11.GL_SCISSOR_TEST);
+ for (int i = 0; i < split.length; i++) {
+ fontRenderer.drawString(split[i].replace("\t", " "), tenth+2,i*fontRenderer.FONT_HEIGHT + 72, 0xFFFFFFFF);
+ }
+ GL11.glDisable(GL11.GL_SCISSOR_TEST);
+
+
+ GlStateManager.pushMatrix();
+ GlStateManager.scale(1.0/sr.getScaleFactor(), 1.0/sr.getScaleFactor(), 1);
+ GlStateManager.translate(0, Minecraft.getMinecraft().displayHeight - qrCode.getHeight() * 3, 0);
+ Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+ float f = 0.0F;
+ Minecraft.getMinecraft().getTextureManager().bindTexture(this.location);
+ GlStateManager.enableBlend();
+ GlStateManager.tryBlendFuncSeparate(1, 771, 0, 1);
+ GlStateManager.disableAlpha();
+ worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX);
+ worldrenderer.pos(0, qrCode.getHeight()*3, 0).tex(0.0D, 1.0D).endVertex();
+ worldrenderer.pos(qrCode.getWidth()*3, qrCode.getHeight()*3, 0).tex(1.0D, 1.0D).endVertex();
+ worldrenderer.pos(qrCode.getWidth()*3, 0, 0).tex(1.0D, 0.0D).endVertex();
+ worldrenderer.pos(0, 0, 0).tex(0.0D, 0.0D).endVertex();
+ tessellator.draw();
+ GlStateManager.enableAlpha();
+ GlStateManager.disableBlend();
+ GlStateManager.popMatrix();
+
+ super.drawScreen(mouseX, mouseY, partialTicks);
+ }
+
+ public static void clip(ScaledResolution resolution, int x, int y, int width, int height) {
+ if (width < 0 || height < 0) return;
+
+ int scale = resolution.getScaleFactor();
+ GL11.glScissor((x ) * scale, Minecraft.getMinecraft().displayHeight - (y + height) * scale, (width) * scale, height * scale);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiPrivacyPolicy.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiPrivacyPolicy.java
new file mode 100644
index 00000000..b7d21b16
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiPrivacyPolicy.java
@@ -0,0 +1,53 @@
+package kr.syeyoung.dungeonsguide.launcher.gui;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.*;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraftforge.fml.common.FMLCommonHandler;
+import org.lwjgl.opengl.GL11;
+
+import java.io.IOException;
+
+public class GuiPrivacyPolicy extends GuiScreen {
+ @Override
+ public void initGui() {
+ ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft());
+ this.buttonList.add(new GuiButton(0, sr.getScaledWidth()/2+50,sr.getScaledHeight()-40, 300, 20,"Accept Privacy Policy"));
+ this.buttonList.add(new GuiButton(1, sr.getScaledWidth()/2-50-300,sr.getScaledHeight()-40, 300, 20,"Deny and Play Without DG"));
+ }
+
+
+ @Override
+ protected void actionPerformed(GuiButton button) throws IOException {
+ super.actionPerformed(button);
+ if (button.id == 0) {
+ // accept
+ } else if (button.id == 1) {
+ Minecraft.getMinecraft().displayGuiScreen(null);
+ }
+ }
+
+
+ @Override
+ public void drawScreen(int mouseX, int mouseY, float partialTicks) {
+ super.drawBackground(0);
+
+ ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft());
+ FontRenderer fontRenderer = Minecraft.getMinecraft().fontRendererObj;
+
+ fontRenderer.drawString("", (sr.getScaledWidth()-fontRenderer.getStringWidth("Please accept or deny Dungeons Guide Privacy Policy to continue"))/2,40,0xFFFF0000);
+
+
+ super.drawScreen(mouseX, mouseY, partialTicks);
+ }
+
+ public static void clip(ScaledResolution resolution, int x, int y, int width, int height) {
+ if (width < 0 || height < 0) return;
+
+ int scale = resolution.getScaleFactor();
+ GL11.glScissor((x ) * scale, Minecraft.getMinecraft().displayHeight - (y + height) * scale, (width) * scale, height * scale);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/IDGLoader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/IDGLoader.java
new file mode 100644
index 00000000..53d9b70d
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/IDGLoader.java
@@ -0,0 +1,39 @@
+/*
+ * 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.loader;
+
+import kr.syeyoung.dungeonsguide.launcher.DGInterface;
+import kr.syeyoung.dungeonsguide.launcher.authentication.Authenticator;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.ReferenceLeakedException;
+import net.minecraftforge.common.config.Configuration;
+
+import java.io.InputStream;
+
+public interface IDGLoader {
+ void loadJar(Authenticator authenticator) throws InstantiationException, IllegalAccessException, ClassNotFoundException;
+ DGInterface getInstance();
+ void unloadJar() throws ReferenceLeakedException;
+
+ boolean isUnloadable();
+
+ boolean isLoaded();
+
+ String strategyName();
+ String version();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java
new file mode 100644
index 00000000..0b6cf124
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java
@@ -0,0 +1,134 @@
+/*
+ * 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.loader;
+
+import kr.syeyoung.dungeonsguide.launcher.DGInterface;
+import kr.syeyoung.dungeonsguide.launcher.Main;
+import kr.syeyoung.dungeonsguide.launcher.authentication.Authenticator;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.ReferenceLeakedException;
+
+import java.io.InputStream;
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class JarLoader implements IDGLoader {
+ private DGInterface dgInterface;
+ private ReferenceQueue<ClassLoader> refQueue = new ReferenceQueue<>();
+ private PhantomReference<ClassLoader> phantomReference;
+
+ private boolean loaded;
+
+ public static class JarClassLoader extends URLClassLoader {
+ public JarClassLoader(URL[] urls, ClassLoader parent) {
+ super(urls, parent);
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+
+ synchronized (getClassLoadingLock(name)) {
+ Class<?> c = findLoadedClass(name);
+ if (c == null) {
+
+ try {
+ if (c == null) {
+ long t0 = System.nanoTime();
+ c = findClass(name);
+
+ sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t0);
+ sun.misc.PerfCounter.getFindClasses().increment();
+ }
+ } catch (ClassNotFoundException e) {
+ // ClassNotFoundException thrown if class not found
+ // from the non-null parent class loader
+ }
+ if (getParent() != null && c == null) {
+ long t0 = System.nanoTime();
+ c = getParent().loadClass(name);
+ long t1 = System.nanoTime();
+ sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
+ }
+ }
+ if (resolve) {
+ resolveClass(c);
+ }
+ return c;
+ }
+ }
+
+ public Class<?> loadClassResolve(String name, boolean resolve) throws ClassNotFoundException {
+ return this.loadClass(name, resolve);
+ }
+ }
+
+ private JarClassLoader classLoader;
+
+ @Override
+ public void loadJar(Authenticator authenticator) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ if (dgInterface != null) throw new IllegalStateException("Already loaded");
+
+ classLoader = new JarClassLoader(new URL[] {
+ Main.class.getResource("/mod.jar")
+ }, this.getClass().getClassLoader());
+
+ dgInterface = (DGInterface) classLoader.loadClassResolve("kr.syeyoung.dungeonsguide.DungeonsGuide", true).newInstance();
+ phantomReference = new PhantomReference<>(classLoader, refQueue);
+ }
+
+ @Override
+ public DGInterface getInstance() {
+ return dgInterface;
+ }
+
+ @Override
+ public void unloadJar() throws ReferenceLeakedException {
+ classLoader = null;
+ dgInterface.unload();
+ dgInterface = null;
+ System.gc();// pls do
+ Reference<? extends ClassLoader> t = refQueue.poll();
+ if (t == null) throw new ReferenceLeakedException(); // Why do you have to be that strict? Well, to tell them to actually listen on DungeonsGuideReloadListener. If it starts causing issues then I will remove check cus it's not really loaded (classes are loaded by child classloader)
+ t.clear();
+ phantomReference = null;
+ }
+
+ @Override
+ public boolean isUnloadable() {
+ return true;
+ }
+
+ @Override
+ public boolean isLoaded() {
+ return dgInterface != null;
+ }
+
+ @Override
+ public String strategyName() {
+ return "jar";
+ }
+
+ @Override
+ public String version() {
+ return "unknown"; // maybe read the thing...
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java
new file mode 100644
index 00000000..1338138d
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java
@@ -0,0 +1,64 @@
+/*
+ * 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.loader;
+
+import kr.syeyoung.dungeonsguide.launcher.DGInterface;
+import kr.syeyoung.dungeonsguide.launcher.authentication.Authenticator;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.ReferenceLeakedException;
+
+import java.io.InputStream;
+
+public class LocalLoader implements IDGLoader {
+ private DGInterface dgInterface;
+
+ @Override
+ public void loadJar(Authenticator authenticator) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ if (dgInterface != null) throw new IllegalStateException("Already loaded");
+ dgInterface = (DGInterface) Class.forName("kr.syeyoung.dungeonsguide.DungeonsGuide").newInstance();
+ }
+
+ @Override
+ public DGInterface getInstance() {
+ return dgInterface;
+ }
+
+ @Override
+ public void unloadJar() throws ReferenceLeakedException {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean isUnloadable() {
+ return false;
+ }
+
+ @Override
+ public boolean isLoaded() {
+ return dgInterface != null;
+ }
+
+ @Override
+ public String strategyName() {
+ return "local";
+ }
+
+ @Override
+ public String version() {
+ return "unknown";
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGConnection.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGConnection.java
new file mode 100755
index 00000000..649dfdc5
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGConnection.java
@@ -0,0 +1,47 @@
+/*
+ * 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.url;
+
+import kr.syeyoung.dungeonsguide.launcher.authentication.Authenticator;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+
+public class DGConnection extends URLConnection {
+ private final Authenticator authenticator;
+ protected DGConnection(URL url, Authenticator a) {
+ super(url);
+ connected = false;
+ this.authenticator = a;
+ }
+
+ @Override
+ public void connect() throws IOException {
+ }
+ @Override
+ public InputStream getInputStream() throws IOException {
+ if (authenticator != null) {
+ String path = url.getPath().substring(1);
+// if (!authenticator.getResources().containsKey(path)) throw new FileNotFoundException();
+// return new ByteArrayInputStream(authenticator.getResources().get(path));
+ }
+ throw new FileNotFoundException();
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandler.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandler.java
new file mode 100755
index 00000000..82ddff18
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandler.java
@@ -0,0 +1,36 @@
+/*
+ * 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.url;
+
+import kr.syeyoung.dungeonsguide.launcher.authentication.Authenticator;
+import lombok.AllArgsConstructor;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+@AllArgsConstructor
+public class DGStreamHandler extends URLStreamHandler {
+ private final Authenticator auth;
+ @Override
+ protected URLConnection openConnection(URL url) throws IOException {
+ return new DGConnection(url, this.auth);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandlerFactory.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandlerFactory.java
new file mode 100755
index 00000000..a7b50046
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandlerFactory.java
@@ -0,0 +1,37 @@
+/*
+ * 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.url;
+
+import kr.syeyoung.dungeonsguide.launcher.authentication.Authenticator;
+import lombok.AllArgsConstructor;
+
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+
+@AllArgsConstructor
+public class DGStreamHandlerFactory implements URLStreamHandlerFactory {
+ private final Authenticator auth;
+ @Override
+ public URLStreamHandler createURLStreamHandler(String protocol) {
+ if ("z".equals(protocol)) {
+ return new DGStreamHandler(this.auth);
+ }
+ return null;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/util/QRCodeGenerator.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/util/QRCodeGenerator.java
new file mode 100644
index 00000000..d0c3306a
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/util/QRCodeGenerator.java
@@ -0,0 +1,43 @@
+/*
+ * 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.util;
+
+import io.nayuki.qrcodegen.QrCode;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.GZIPOutputStream;
+
+public class QRCodeGenerator {
+ public static BufferedImage generateQRCode(byte[] bytes) throws IOException {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
+ gzipOutputStream.write(bytes);
+ gzipOutputStream.flush();
+ gzipOutputStream.close();
+
+
+ byte[] array = byteArrayOutputStream.toByteArray();
+
+ QrCode qrCode = QrCode.encodeBinary(array, QrCode.Ecc.LOW);
+ return qrCode.toImage(1, 0);
+ }
+}
diff --git a/loader/src/main/resources/mcmod.info b/loader/src/main/resources/mcmod.info
new file mode 100755
index 00000000..70df65aa
--- /dev/null
+++ b/loader/src/main/resources/mcmod.info
@@ -0,0 +1,16 @@
+[
+{
+ "modid": "skyblock_dungeons_guide",
+ "name": "Skyblock Dungeons Guide",
+ "description": "A mod to help dungeon players to find and solve secrets and puzzles most efficiently.",
+ "version": "${version}",
+ "mcversion": "${mcversion}",
+ "url": "",
+ "updateUrl": "",
+ "authorList": ["syeyoung"],
+ "credits": "The guild Jerry's Crew, for nothing.",
+ "logoFile": "",
+ "screenshots": [],
+ "dependencies": []
+}
+]