aboutsummaryrefslogtreecommitdiff
path: root/loader/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'loader/src/main')
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DungeonsGuideReloadListener.java3
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java150
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/AuthManager.java179
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/AuthUtil.java57
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/DGResponse.java15
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/DgAuthUtil.java163
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/InvalidDungeonsGuideCredentialsException.java8
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/ResourceManager.java164
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/AuthProvider.java16
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/DgAuth/DgAuth.java46
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/DgAuth/DgAuthUtil.java88
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/NullAuth.java62
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/AuthToken.java19
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/DGAuthToken.java57
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/FailedAuthToken.java41
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/NullToken.java31
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/PrivacyPolicyRequiredToken.java56
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/Authenticator.java257
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/UpdateRetrieverUtil.java182
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/AuthChangedEvent.java12
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AssetNotFoundException.java35
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthFailedExeption.java7
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthServerException.java16
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthenticationUnavailableException.java12
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/InvalidSignatureException.java37
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/NoVersionFoundException.java38
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/PrivacyPolicyRequiredException.java2
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/ResponseParsingException.java (renamed from loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/TokenStatus.java)20
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/TokenExpiredException.java2
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiDisplayer.java44
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiLoadingError.java (renamed from loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiLoadingError.java)12
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiPrivacyPolicy.java (renamed from loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiPrivacyPolicy.java)14
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiReferenceLeak.java90
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/SpecialGuiScreen.java17
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/ByteStreamURLHandler.java46
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/DGClassLoader.java81
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/IDGLoader.java10
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java76
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java59
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java152
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/SignatureValidator.java43
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGConnection.java49
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandler.java34
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandlerFactory.java35
44 files changed, 1450 insertions, 1087 deletions
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DungeonsGuideReloadListener.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DungeonsGuideReloadListener.java
index 7252a9db..a96805c1 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DungeonsGuideReloadListener.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DungeonsGuideReloadListener.java
@@ -19,6 +19,9 @@
package kr.syeyoung.dungeonsguide.launcher;
public interface DungeonsGuideReloadListener {
+ /**
+ * @implNote This is very important that you GET RID OF referene to DGInterface when this is called, or else dg is gonna crash with ReferenceLeakedException.
+ */
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
index 7cc0f806..e8d3e36b 100755
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java
@@ -18,25 +18,21 @@
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.auth.AuthManager;
+import kr.syeyoung.dungeonsguide.launcher.branch.UpdateRetrieverUtil;
import kr.syeyoung.dungeonsguide.launcher.exceptions.NoSuitableLoaderFoundException;
-import kr.syeyoung.dungeonsguide.launcher.exceptions.PrivacyPolicyRequiredException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.NoVersionFoundException;
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.gui.screen.GuiDisplayer;
+import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiLoadingError;
+import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiReferenceLeak;
+import kr.syeyoung.dungeonsguide.launcher.gui.screen.SpecialGuiScreen;
import kr.syeyoung.dungeonsguide.launcher.loader.IDGLoader;
import kr.syeyoung.dungeonsguide.launcher.loader.JarLoader;
import kr.syeyoung.dungeonsguide.launcher.loader.LocalLoader;
+import kr.syeyoung.dungeonsguide.launcher.loader.RemoteLoader;
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;
@@ -44,13 +40,9 @@ 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;
+import java.util.*;
@Mod(modid = Main.MOD_ID, version = Main.VERSION)
public class Main
@@ -61,13 +53,15 @@ public class Main
private static Main main;
- private File configDir;
+ private static File configDir;
private DGInterface dgInterface;
- private Authenticator authenticator = new Authenticator();
- private ModDownloader modDownloader = new ModDownloader(authenticator);
- private List<DungeonsGuideReloadListener> listeners = new ArrayList<>();
+ private final List<DungeonsGuideReloadListener> listeners = new ArrayList<>();
+
+ public static File getConfigDir() {
+ return configDir;
+ }
public void addDGReloadListener(DungeonsGuideReloadListener dungeonsGuideReloadListener) {
listeners.add(Objects.requireNonNull(dungeonsGuideReloadListener));
@@ -77,28 +71,18 @@ public class Main
}
private IDGLoader currentLoader;
-
- private Throwable lastError;
- private boolean isMcLoaded;
-
-
-
-
@EventHandler
- public void initEvent(FMLInitializationEvent initializationEvent)
- {
+ public void initEvent(FMLInitializationEvent initializationEvent) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
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);
- }
+ MinecraftForge.EVENT_BUS.register(GuiDisplayer.INSTANCE);
+
+ try {
+ File f = new File(configDir, "loader.cfg");
+ Configuration configuration = new Configuration(f);
+ IDGLoader idgLoader = obtainLoader(configuration);
+ load(idgLoader);
+ } catch (Throwable e) {
+ GuiDisplayer.INSTANCE.displayGui(obtainErrorGUI(e));
}
}
@@ -111,12 +95,14 @@ public class Main
listener.unloadReference();
}
if (currentLoader != null) {
- currentLoader.unloadJar();
+ currentLoader.unloadDungeonsGuide();
}
currentLoader = null;
}
- private void load(IDGLoader newLoader) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
- partialLoad(newLoader);
+ private void load(IDGLoader newLoader) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
+ if (dgInterface != null) throw new IllegalStateException("DG is loaded");
+ dgInterface = newLoader.loadDungeonsGuide();
+ currentLoader = newLoader;
dgInterface.init(configDir);
@@ -124,13 +110,6 @@ public class Main
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();
@@ -140,49 +119,25 @@ public class Main
currentLoader = null;
e.printStackTrace();
- setLastFatalError(e);
- }
- }
- public void tryOpenError() {
- if (isMcLoaded) Minecraft.getMinecraft().displayGuiScreen(obtainErrorGUI());
+ GuiDisplayer.INSTANCE.displayGui(obtainErrorGUI(e));
+ }
}
- public GuiScreen obtainErrorGUI() {
- if (lastError instanceof PrivacyPolicyRequiredException) {
- return new GuiPrivacyPolicy();
- } else if (lastError instanceof TokenExpiredException) {
-
+ public SpecialGuiScreen obtainErrorGUI(Throwable lastError) {
+ if (lastError instanceof kr.syeyoung.dungeonsguide.launcher.exceptions.AuthenticationUnavailableException) {
+ return null;
} else if (lastError instanceof NoSuitableLoaderFoundException) {
-
+ return new GuiLoadingError(lastError);
} else if (lastError instanceof ReferenceLeakedException) {
-
- } else if (lastError instanceof AuthServerException) {
-
- } else if (lastError instanceof InvalidCredentialsException) {
-
- } else if (lastError instanceof AuthenticationUnavailableException) {
-
+ return new GuiReferenceLeak(lastError);
} else if (lastError != null){
- return new GuiLoadingError(lastError, () -> {lastError = null;});
+ return new GuiLoadingError(lastError);
}
- 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");
@@ -205,7 +160,19 @@ public class Main
return new JarLoader();
} else if (loader.equals("auto") ){
// remote load
- throw new UnsupportedOperationException(""); // yet
+ String branch = System.getProperty("branch") == null ? configuration.get("loader", "remoteBranch", "$default").getString() : System.getProperty("branch");
+ String version = System.getProperty("version") == null ? configuration.get("loader", "remoteVersion", "latest").getString() : System.getProperty("version");
+ try {
+ UpdateRetrieverUtil.VersionInfo versionInfo = UpdateRetrieverUtil.getIds(
+ branch,
+ version
+ );
+ if (versionInfo == null) throw new NoVersionFoundException(branch, version);
+
+ return new RemoteLoader(versionInfo.getFriendlyBranchName(), versionInfo.getBranchId(), versionInfo.getUpdateId());
+ } catch (IOException e) {
+ throw new NoVersionFoundException(branch, version, e);
+ }
} else {
throw new NoSuitableLoaderFoundException(System.getProperty("dg.loader"), configuration.get("loader", "modsource", "auto").getString());
}
@@ -218,20 +185,17 @@ public class Main
configDir = preInitializationEvent.getModConfigurationDirectory();
// setup preinit progress bar for well, progress bar!
- ProgressManager.ProgressBar bar = ProgressManager.push("DungeonsGuide", 2);
+ ProgressManager.ProgressBar bar = ProgressManager.push("DungeonsGuide", 1);
try {
// Try authenticate
bar.step("Authenticating...");
- authenticator.repeatAuthenticate(5);
+ AuthManager.getInstance().init();
// 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) {
@@ -239,7 +203,6 @@ public class Main
currentLoader = null;
t.printStackTrace();
- setLastFatalError(t);
} finally {
while(bar.getStep() < bar.getSteps()) bar.step("");
ProgressManager.pop(bar);
@@ -250,11 +213,6 @@ public class Main
});
}
- 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/auth/AuthManager.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/AuthManager.java
index f0c346c6..c3dd747a 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/AuthManager.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/AuthManager.java
@@ -2,149 +2,142 @@ package kr.syeyoung.dungeonsguide.launcher.auth;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import com.google.gson.JsonObject;
import com.mojang.authlib.exceptions.AuthenticationException;
-import kr.syeyoung.dungeonsguide.launcher.auth.authprovider.AuthProvider;
-import kr.syeyoung.dungeonsguide.launcher.auth.authprovider.DgAuth.DgAuth;
-import kr.syeyoung.dungeonsguide.launcher.auth.authprovider.DgAuth.DgAuthUtil;
-import kr.syeyoung.dungeonsguide.mod.chat.ChatTransmitter;
-import kr.syeyoung.dungeonsguide.mod.events.impl.AuthChangedEvent;
-import kr.syeyoung.dungeonsguide.mod.stomp.StompManager;
+import kr.syeyoung.dungeonsguide.launcher.Main;
+import kr.syeyoung.dungeonsguide.launcher.auth.token.*;
+import kr.syeyoung.dungeonsguide.launcher.events.AuthChangedEvent;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.AuthFailedExeption;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.PrivacyPolicyRequiredException;
+import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiDisplayer;
+import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiPrivacyPolicy;
import lombok.Setter;
import net.minecraft.client.Minecraft;
import net.minecraftforge.common.MinecraftForge;
-import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
-import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
-import java.security.KeyPair;
+import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
-import java.util.Objects;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
+import java.security.spec.InvalidKeySpecException;
+import java.util.concurrent.*;
public class AuthManager {
Logger logger = LogManager.getLogger("AuthManger");
- private static AuthManager instance;
+ private static AuthManager INSTANCE;
public static AuthManager getInstance() {
- if(instance == null) instance = new AuthManager();
- return instance;
+ if(INSTANCE == null) INSTANCE = new AuthManager();
+ return INSTANCE;
}
+ private AuthToken currentToken = new NullToken();
-
- @Setter
- private String baseserverurl = "https://dungeons.guide";
-
- private AuthProvider currentProvider;
-
- public String getToken() {
- if (currentProvider != null && currentProvider.getToken() != null) {
- return currentProvider.getToken();
- }
- return null;
+ public AuthToken getToken() {
+ return currentToken;
+ }
+ public String getWorkingTokenOrNull() {
+ if (currentToken instanceof DGAuthToken) return currentToken.getToken();
+ else return null;
}
- public KeyPair getKeyPair(){
- if (currentProvider != null && currentProvider.getToken() != null) {
- return currentProvider.getRsaKey();
- }
- return null;
+ public String getWorkingTokenOrThrow() {
+ if (currentToken instanceof DGAuthToken) return currentToken.getToken();
+ else if (currentToken instanceof FailedAuthToken) throw new AuthFailedExeption(((FailedAuthToken) currentToken).getException());
+ else if (currentToken instanceof NullToken) throw new IllegalStateException("No Token");
+ else if (currentToken instanceof PrivacyPolicyRequiredToken) throw new PrivacyPolicyRequiredException();
+ throw new IllegalStateException("weird token: "+currentToken);
}
- boolean initlock = false;
+ private volatile boolean initlock = false;
public void init() {
if (initlock) {
logger.info("Cannot init AuthManger twice");
- return;
+ throw new IllegalStateException("Can not init AuthManager twice");
}
- reauth();
-
initlock = true;
-
- MinecraftForge.EVENT_BUS.register(this);
-
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("DgAuth Pool").build();
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, namedThreadFactory);
scheduler.scheduleAtFixedRate(() -> {
- if (getToken() != null) {
- JsonObject obj = DgAuthUtil.getJwtPayload(getToken());
- if (!obj.get("uuid").getAsString().replace("-", "").equals(Minecraft.getMinecraft().getSession().getPlayerID())) {
- shouldReAuth = true;
- }
+ boolean shouldReAuth = false;
+ if (!getToken().getUID().replace("-", "").equals(Minecraft.getMinecraft().getSession().getPlayerID())) {
+ shouldReAuth = true;
+ }
+ if (!getToken().isAuthenticated()) {
+ shouldReAuth = true;
}
+ if (shouldReAuth)
+ reAuth();
}, 10,2000, TimeUnit.MILLISECONDS);
- }
-
- boolean shouldReAuth = true;
- int tickCounter;
- @SubscribeEvent
- public void onTickClientTick(TickEvent.ClientTickEvent event) {
- if (event.phase != TickEvent.Phase.START) return;
- if (tickCounter % 200 == 0) {
- tickCounter = 0;
- reauth();
- }
- tickCounter++;
-
- }
-
- public boolean isPlebUser(){
- return Objects.equals(getInstance().getPlanType(), "OPENSOURCE");
+ reAuth();
}
- public String getPlanType(){
- if(getToken() == null) return null;
+ private volatile boolean reauthLock = false;
- JsonObject jwt = DgAuthUtil.getJwtPayload(getToken());
- if(!jwt.has("plan")) return null;
+ AuthToken reAuth() {
+ if (reauthLock) {
+ while (reauthLock) ;
+ return currentToken;
+ }
- return jwt.get("plan").getAsString();
+ reauthLock = true;
+ try {
+ String token = DgAuthUtil.requestAuth();
+ byte[] encSecret = DgAuthUtil.checkSessionAuthenticityAndReturnEncryptedSecret(token);
+ currentToken = DgAuthUtil.verifyAuth(token, encSecret);
+ MinecraftForge.EVENT_BUS.post(new AuthChangedEvent(currentToken));
+
+ if (currentToken instanceof PrivacyPolicyRequiredToken) {
+ GuiDisplayer.INSTANCE.displayGui(new GuiPrivacyPolicy());
+ throw new PrivacyPolicyRequiredException();
+ }
+ } catch (NoSuchAlgorithmException | AuthenticationException | IOException | NoSuchPaddingException |
+ InvalidKeyException | InvalidKeySpecException | IllegalBlockSizeException | BadPaddingException e) {
+ currentToken = new FailedAuthToken(e);
+ // TODO: loader notifications on bottom right?
+// ChatTransmitter.addToQueue("§eDungeons Guide §7:: §r§cDG auth failed, trying again in ten seconds", true);
+ logger.error("Re-auth failed with message {}, trying again in a 2 seconds", String.valueOf(Throwables.getRootCause(e)));
+ throw new AuthFailedExeption(e);
+ } finally {
+ reauthLock = false;
+ }
+ return currentToken;
}
- void reauth() {
- if (!shouldReAuth) return;
- shouldReAuth = false;
+ AuthToken acceptPrivacyPolicy() throws IOException {
+ if (reauthLock) {
+ while(reauthLock);
+ return currentToken;
+ }
- currentProvider = null;
- try {
- currentProvider = new DgAuth(baseserverurl).createAuthProvider();
- if (currentProvider.getToken() == null) {
- shouldReAuth = true;
- currentProvider = null;
- ChatTransmitter.addToQueue("§eDungeons Guide §7:: §r§cDG auth failed, trying again in ten seconds", true);
- logger.info("DG auth failed, trying again in a second");
- } else {
- // RE-AUTHed SUCCESSFULLY HOORAY
- // for some reason the forge events don't work in pre init, so I call the callback directly
- StompManager.getInstance().init();
- MinecraftForge.EVENT_BUS.post(new AuthChangedEvent());
+ if (currentToken instanceof PrivacyPolicyRequiredToken) {
+ reauthLock = true;
+ try {
+ currentToken = DgAuthUtil.acceptNewPrivacyPolicy(currentToken.getToken());
+ if (currentToken instanceof PrivacyPolicyRequiredToken) throw new PrivacyPolicyRequiredException();
+ } catch (IOException e) {
+ currentToken = new FailedAuthToken(e);
+ // TODO: loader notifications on bottom right?
+ logger.error("Accepting Privacy Policy failed with message {}, trying again in a 2 seconds", String.valueOf(Throwables.getRootCause(e)));
+ throw e;
+ } finally {
+ reauthLock = false;
}
- } catch (NoSuchAlgorithmException | AuthenticationException | IOException e) {
-
- shouldReAuth = true;
- currentProvider = null;
- ChatTransmitter.addToQueue("§eDungeons Guide §7:: §r§cDG auth failed, trying again in ten seconds", true);
- logger.error("Re-auth failed with message {}, trying again in a ten seconds", String.valueOf(Throwables.getRootCause(e)));
}
-
+ return currentToken;
}
-
-
}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/AuthUtil.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/AuthUtil.java
index 9ce02643..e5408f0b 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/AuthUtil.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/AuthUtil.java
@@ -12,59 +12,16 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.*;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
public class AuthUtil {
private AuthUtil() {}
- public static KeyPair getKeyPair() throws NoSuchAlgorithmException {
- KeyPairGenerator a = null;
- a = KeyPairGenerator.getInstance("RSA");
- a.initialize(1024);
- return a.generateKeyPair();
- }
-
-
- public static JsonElement getJsonSecured(String u) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException{
-
- int length = 0;
- CipherInputStream cipherInputStream = null;
-
- 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", AuthManager.getInstance().getToken());
- httpsURLConnection.setDoInput(true);
- httpsURLConnection.setDoOutput(true);
-
- InputStream inputStream = httpsURLConnection.getInputStream();
- byte[] lengthPayload = new byte[4];
- inputStream.read(lengthPayload);
- 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, AuthManager.getInstance().getKeyPair().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 = new CipherInputStream(inputStream, cipher);
- cipherInputStream.read(lengthPayload);
- length = ((lengthPayload[0] & 0xFF) << 24) |
- ((lengthPayload[1] & 0xFF) << 16) |
- ((lengthPayload[2] & 0xFF) << 8) |
- ((lengthPayload[3] & 0xFF));
-
- httpsURLConnection.disconnect();
-
- return new JsonParser().parse(new InputStreamReader(cipherInputStream));
+ public static PublicKey getPublicKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
+ PublicKey publicKey =
+ KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(bytes));
+ publicKey.getEncoded();
+ return publicKey;
}
}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/DGResponse.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/DGResponse.java
new file mode 100644
index 00000000..5873fbda
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/DGResponse.java
@@ -0,0 +1,15 @@
+package kr.syeyoung.dungeonsguide.launcher.auth;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.json.JSONObject;
+
+@Data
+@AllArgsConstructor
+public class DGResponse<T> {
+ private final String status;
+ private final T data;
+ private final String errorMessage;
+ private final String qrCode;
+}
+
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/DgAuthUtil.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/DgAuthUtil.java
new file mode 100644
index 00000000..78fc91c9
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/DgAuthUtil.java
@@ -0,0 +1,163 @@
+package kr.syeyoung.dungeonsguide.launcher.auth;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.exceptions.AuthenticationException;
+import com.mojang.authlib.minecraft.MinecraftSessionService;
+import kr.syeyoung.dungeonsguide.launcher.Main;
+import kr.syeyoung.dungeonsguide.launcher.auth.token.AuthToken;
+import kr.syeyoung.dungeonsguide.launcher.auth.token.DGAuthToken;
+import kr.syeyoung.dungeonsguide.launcher.auth.token.PrivacyPolicyRequiredToken;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.AuthServerException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.PrivacyPolicyRequiredException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.ResponseParsingException;
+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 javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.net.ssl.HttpsURLConnection;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.net.URL;
+import java.security.*;
+import java.security.spec.InvalidKeySpecException;
+import java.util.stream.Collectors;
+
+public class DgAuthUtil {
+ private static final SecureRandom random = new SecureRandom();
+ private DgAuthUtil(){}
+
+
+ private static <T> DGResponse<T> getResponse(HttpsURLConnection connection, Class<T> data) throws IOException {
+ connection.getResponseCode();
+ InputStream toRead = connection.getErrorStream();
+ if (toRead == null)
+ toRead = connection.getInputStream();
+ String payload = IOUtils.readLines(toRead).stream().collect(Collectors.joining("\n"));
+
+ try {
+ JSONObject json = new JSONObject(payload);
+ return new DGResponse(
+ json.getString("status"),
+ (T) json.get("data"),
+ json.getString("errorMessage"),
+ json.getString("qrCode")
+ );
+ } catch (Exception e) {
+ throw new ResponseParsingException(payload, e.getMessage());
+ } finally {
+ toRead.close();
+ }
+
+ }
+
+ public static String requestAuth() throws IOException {
+ GameProfile profile = Minecraft.getMinecraft().getSession().getProfile();
+
+ HttpsURLConnection connection = (HttpsURLConnection) new URL(Main.DOMAIN + "/auth/requestAuth").openConnection();
+ connection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ connection.setRequestProperty("Content-Type", "application/json");
+ connection.setRequestMethod("POST");
+ connection.setDoInput(true);
+ connection.setDoOutput(true);
+
+ connection.getOutputStream().write(("{\"uuid\":\""+profile.getId().toString()+"\",\"nickname\":\""+profile.getName()+"\"}").getBytes());
+
+ DGResponse<String> preToken = getResponse(connection, String.class);
+ if (!"SUCCESS".equals(preToken.getStatus())) {
+ throw new AuthServerException(preToken);
+ }
+
+ return preToken.getData();
+ }
+
+ public static byte[] checkSessionAuthenticityAndReturnEncryptedSecret(String tempToken) throws NoSuchAlgorithmException, AuthenticationException, NoSuchPaddingException, InvalidKeySpecException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ JSONObject d = getJwtPayload(tempToken);
+ byte[] sharedSecret = new byte[16];
+ random.nextBytes(sharedSecret);
+ byte[] publicKey =Base64.decodeBase64(d.getString("publicKey"));
+
+ Cipher cipher = Cipher.getInstance("RSA");
+ cipher.init(Cipher.ENCRYPT_MODE, AuthUtil.getPublicKey(publicKey));
+ byte[] result = cipher.doFinal(sharedSecret);
+
+
+ String hash = calculateServerHash(sharedSecret, publicKey);
+
+ Session session = Minecraft.getMinecraft().getSession();
+ MinecraftSessionService yggdrasilMinecraftSessionService = Minecraft.getMinecraft().getSessionService();
+ yggdrasilMinecraftSessionService.joinServer(session.getProfile(), session.getToken(), hash);
+
+ return result;
+ }
+
+ public static AuthToken verifyAuth(String tempToken, byte[] encSecret) throws IOException {
+ HttpsURLConnection urlConnection = (HttpsURLConnection) new URL(Main.DOMAIN + "/auth/authenticate").openConnection();
+ urlConnection.setRequestMethod("POST");
+ urlConnection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ urlConnection.setRequestProperty("Content-Type", "application/json");
+ urlConnection.setDoInput(true);
+ urlConnection.setDoOutput(true);
+
+ urlConnection.getOutputStream().write(("{\"jwt\":\""+tempToken+"\",\"sharedSecret\":\""+Base64.encodeBase64URLSafeString(encSecret)+"\"}").getBytes());
+
+ DGResponse<JSONObject> postToken = getResponse(urlConnection, JSONObject.class);
+ if (!"SUCCESS".equals(postToken.getStatus())) {
+ throw new AuthServerException(postToken);
+ }
+ JSONObject data = postToken.getData();
+ if (data.getString("result").equals("TOS_PRIVACY_POLICY_ACCEPT_REQUIRED")) {
+ return new PrivacyPolicyRequiredToken(data.getString("jwt"));
+ } else if (data.getString("result").equals("SUCCESSFUL")) {
+ return new DGAuthToken(data.getString("jwt"));
+ } else {
+ throw new AuthServerException(postToken);
+ }
+ }
+
+ public static AuthToken acceptNewPrivacyPolicy(String tempToken) throws IOException {
+ HttpsURLConnection urlConnection = (HttpsURLConnection) new URL(Main.DOMAIN + "/auth/acceptPrivacyPolicy").openConnection();
+ urlConnection.setRequestMethod("POST");
+ urlConnection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ urlConnection.setRequestProperty("Content-Type", "application/json");
+ urlConnection.setDoInput(true);
+ urlConnection.setDoOutput(true);
+
+ urlConnection.getOutputStream().write(tempToken.getBytes());
+
+ DGResponse<JSONObject> postToken = getResponse(urlConnection, JSONObject.class);
+ if (!"SUCCESS".equals(postToken.getStatus())) {
+ throw new AuthServerException(postToken);
+ }
+ JSONObject data = postToken.getData();
+ if (data.getString("result").equals("TOS_PRIVACY_POLICY_ACCEPT_REQUIRED")) {
+ return new PrivacyPolicyRequiredToken(data.getString("jwt"));
+ } else if (data.getString("result").equals("SUCCESSFUL")) {
+ return new DGAuthToken(data.getString("jwt"));
+ } else {
+ throw new AuthServerException(postToken);
+ }
+ }
+ public static JSONObject getJwtPayload(String jwt) {
+ String midPart = jwt.split("\\.")[1].replace("+", "-").replace("/", "_");
+ String base64Decode = new String(Base64.decodeBase64(midPart)); // padding
+ return new JSONObject(base64Decode);
+ }
+
+ public static 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);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/InvalidDungeonsGuideCredentialsException.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/InvalidDungeonsGuideCredentialsException.java
deleted file mode 100644
index ebe78196..00000000
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/InvalidDungeonsGuideCredentialsException.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package kr.syeyoung.dungeonsguide.launcher.auth;
-
-public class InvalidDungeonsGuideCredentialsException extends Throwable {
-
- public InvalidDungeonsGuideCredentialsException(String message) {
- super(message);
- }
-}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/ResourceManager.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/ResourceManager.java
deleted file mode 100644
index 721b629f..00000000
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/ResourceManager.java
+++ /dev/null
@@ -1,164 +0,0 @@
-package kr.syeyoung.dungeonsguide.launcher.auth;
-
-import lombok.Setter;
-import net.minecraftforge.common.MinecraftForge;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import javax.crypto.*;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import javax.net.ssl.HttpsURLConnection;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.security.*;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-public class ResourceManager {
-
- Logger logger = LogManager.getLogger("ResourceManager");
-
- @Setter
- private String baseUrl;
- @Setter
- private String BASE64_X509ENCODEDKEYSPEC;
- private final HashMap<String, byte[]> loadedResources = new HashMap<>();
-
-
- private static ResourceManager instance;
- public static ResourceManager getInstance() {
- if(instance == null) {
- instance = new ResourceManager();
- MinecraftForge.EVENT_BUS.register(instance);
- }
- return instance;
- }
-
- private ResourceManager() {
- }
-
- public Map<String, byte[]> getResources() {
- return loadedResources;
- }
-
-
- public void downloadAssets(String version) throws InvalidDungeonsGuideCredentialsException {
- if(AuthManager.getInstance().getToken() == null) throw new InvalidDungeonsGuideCredentialsException("Not Authenticated while downloading assets");
- try {
- // version not being null indicates that the user is "premium"
- // so we download the special version
- if (version != null)
- downloadSafe( baseUrl + "/resource/version?v=" + version, true);
-
- if(!AuthManager.getInstance().isPlebUser()){
- downloadSafe(baseUrl + "/resource/roomdata", false);
- } else {
- logger.error("The current User is a pleb not downloading user data");
- }
-
- } catch (Exception t) {
- t.printStackTrace();
- }
-
- }
-
- private void downloadSafe(String url, boolean isValidateSignature) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, 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", AuthManager.getInstance().getToken());
- 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, AuthManager.getInstance().getKeyPair().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;
- if (isValidateSignature) {
- 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(getPublicKey(BASE64_X509ENCODEDKEYSPEC));
- 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;
- while((p = zipInputStream.read(buffer)) > 0) {
- byteArrayOutputStream.write(buffer, 0, p);
- }
- this.loadedResources.put(zipEntry.getName(), byteArrayOutputStream.toByteArray());
- }
- }
-
-
- public static PublicKey getPublicKey(String base64X509EncodedKeySpec) throws NoSuchAlgorithmException, InvalidKeySpecException {
- X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decodeBase64(base64X509EncodedKeySpec));
-
- return KeyFactory.getInstance("RSA").generatePublic(spec);
- }
-
-}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/AuthProvider.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/AuthProvider.java
deleted file mode 100644
index d469c729..00000000
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/AuthProvider.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package kr.syeyoung.dungeonsguide.launcher.auth.authprovider;
-
-import com.mojang.authlib.exceptions.AuthenticationException;
-
-import java.io.IOException;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-
-public interface AuthProvider {
- String getToken();
-
- KeyPair getRsaKey();
-
-
- AuthProvider createAuthProvider() throws NoSuchAlgorithmException, AuthenticationException, IOException;
-}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/DgAuth/DgAuth.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/DgAuth/DgAuth.java
deleted file mode 100644
index dca9ce33..00000000
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/DgAuth/DgAuth.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package kr.syeyoung.dungeonsguide.launcher.auth.authprovider.DgAuth;
-
-import com.mojang.authlib.exceptions.AuthenticationException;
-import kr.syeyoung.dungeonsguide.launcher.auth.AuthUtil;
-import kr.syeyoung.dungeonsguide.launcher.auth.authprovider.AuthProvider;
-
-import java.io.IOException;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-
-public class DgAuth implements AuthProvider {
-
- private final String authServerUrl;
-
- public DgAuth(String authServerUrl){
- this.authServerUrl = authServerUrl;
- }
-
- private String token;
- private KeyPair rsaKey;
-
- @Override
- public String getToken() {
- return token;
- }
-
- @Override
- public KeyPair getRsaKey() {
- return rsaKey;
- }
-
-
- @Override
- public AuthProvider createAuthProvider() throws NoSuchAlgorithmException, AuthenticationException, IOException {
- this.rsaKey = AuthUtil.getKeyPair();
-
- String tempToken = DgAuthUtil.requestAuth(this.authServerUrl);
-
- DgAuthUtil.checkSessionAuthenticity(tempToken);
-
- this.token = DgAuthUtil.verifyAuth(tempToken, rsaKey.getPublic(), authServerUrl);
-
- return this;
- }
-
-}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/DgAuth/DgAuthUtil.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/DgAuth/DgAuthUtil.java
deleted file mode 100644
index 53b57e1a..00000000
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/DgAuth/DgAuthUtil.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package kr.syeyoung.dungeonsguide.launcher.auth.authprovider.DgAuth;
-
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.mojang.authlib.GameProfile;
-import com.mojang.authlib.exceptions.AuthenticationException;
-import com.mojang.authlib.minecraft.MinecraftSessionService;
-import net.minecraft.client.Minecraft;
-import net.minecraft.util.Session;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.io.IOUtils;
-
-import javax.net.ssl.HttpsURLConnection;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.net.URL;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-
-public class DgAuthUtil {
- private DgAuthUtil(){}
-
- public static String requestAuth(String baseurl) throws IOException {
- GameProfile profile = Minecraft.getMinecraft().getSession().getProfile();
-
- HttpsURLConnection connection = (HttpsURLConnection) new URL(baseurl + "/auth/requestAuth").openConnection();
- connection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
- connection.setRequestProperty("Content-Type", "application/json");
- connection.setRequestMethod("POST");
- connection.setDoInput(true);
- connection.setDoOutput(true);
-
- connection.getOutputStream().write(("{\"uuid\":\""+profile.getId().toString()+"\",\"nickname\":\""+profile.getName()+"\"}").getBytes());
- String payload = String.join("\n", IOUtils.readLines(connection.getErrorStream() == null ? connection.getInputStream() : connection.getErrorStream()));
-
- JsonObject json = (JsonObject) new JsonParser().parse(payload);
-
- if (!"ok".equals(json.get("status").getAsString())) {
- return null;
- }
- return json.get("data").getAsString();
- }
-
- public static void checkSessionAuthenticity(String tempToken) throws NoSuchAlgorithmException, AuthenticationException {
- JsonObject d = getJwtPayload(tempToken);
- byte[] sharedSecret = Base64.decodeBase64(d.get("sharedSecret").getAsString());
- byte[] publicKey =Base64.decodeBase64(d.get("publicKey").getAsString());
- String hash = calculateServerHash(sharedSecret, publicKey);
-
- Session session = Minecraft.getMinecraft().getSession();
- MinecraftSessionService yggdrasilMinecraftSessionService = Minecraft.getMinecraft().getSessionService();
- yggdrasilMinecraftSessionService.joinServer(session.getProfile(), session.getToken(), hash);
- }
-
- public static String verifyAuth(String tempToken, PublicKey clientKey, String baseurl) throws IOException {
- HttpsURLConnection urlConnection = (HttpsURLConnection) new URL(baseurl + "/auth/authenticate").openConnection();
- urlConnection.setRequestMethod("POST");
- urlConnection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
- urlConnection.setRequestProperty("Content-Type", "application/json");
- urlConnection.setDoInput(true);
- urlConnection.setDoOutput(true);
-
- urlConnection.getOutputStream().write(("{\"jwt\":\""+tempToken+"\",\"publicKey\":\""+Base64.encodeBase64URLSafeString(clientKey.getEncoded())+"\"}").getBytes());
- String payload = String.join("\n", IOUtils.readLines(urlConnection.getErrorStream() == null ? urlConnection.getInputStream() : urlConnection.getErrorStream()));
-
- JsonObject jsonObject = (JsonObject) new JsonParser().parse(payload);
- if (!"ok".equals(jsonObject.get("status").getAsString())) {
- return null;
- }
- return jsonObject.get("data").getAsString();
- }
-
- public static JsonObject getJwtPayload(String jwt) {
- String midPart = jwt.split("\\.")[1].replace("+", "-").replace("/", "_");
- String base64Decode = new String(Base64.decodeBase64(midPart)); // padding
- return (JsonObject) new JsonParser().parse(base64Decode);
- }
-
- public static 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);
- }
-}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/NullAuth.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/NullAuth.java
deleted file mode 100644
index 303cfb0a..00000000
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/authprovider/NullAuth.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package kr.syeyoung.dungeonsguide.launcher.auth.authprovider;
-
-import com.mojang.authlib.exceptions.AuthenticationException;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.io.IOException;
-import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-
-public class NullAuth implements AuthProvider {
-
- Logger logger = LogManager.getLogger("NullAuth");
-
- @Override
- public String getToken() {
- return "TOKEN";
- }
-
- @Override
- public KeyPair getRsaKey() {
- return new KeyPair(new PublicKey() {
- @Override
- public String getAlgorithm() {
- return null;
- }
-
- @Override
- public String getFormat() {
- return null;
- }
-
- @Override
- public byte[] getEncoded() {
- return new byte[0];
- }
- }, new PrivateKey() {
- @Override
- public String getAlgorithm() {
- return null;
- }
-
- @Override
- public String getFormat() {
- return null;
- }
-
- @Override
- public byte[] getEncoded() {
- return new byte[0];
- }
- });
- }
-
- @Override
- public AuthProvider createAuthProvider() throws NoSuchAlgorithmException, AuthenticationException, IOException {
- return new NullAuth();
- }
-
-}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/AuthToken.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/AuthToken.java
new file mode 100644
index 00000000..6bbfec7b
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/AuthToken.java
@@ -0,0 +1,19 @@
+package kr.syeyoung.dungeonsguide.launcher.auth.token;
+
+import java.security.KeyPair;
+import java.security.interfaces.RSAKey;
+import java.time.Instant;
+
+public interface AuthToken {
+ boolean isUserVerified();
+ boolean hasFullCapability();
+ boolean isAuthenticated();
+
+ Instant getExpiryInstant();
+
+ default String getUID() {return null;}
+ default String getUUID() {return null;}
+ default String getUsername() {return null;}
+
+ String getToken();
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/DGAuthToken.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/DGAuthToken.java
new file mode 100644
index 00000000..702c23a1
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/DGAuthToken.java
@@ -0,0 +1,57 @@
+package kr.syeyoung.dungeonsguide.launcher.auth.token;
+
+import kr.syeyoung.dungeonsguide.launcher.auth.AuthUtil;
+import kr.syeyoung.dungeonsguide.launcher.auth.DgAuthUtil;
+import org.json.JSONObject;
+
+import java.time.Instant;
+
+public class DGAuthToken implements AuthToken {
+ private String token;
+ private JSONObject parsed;
+
+ public DGAuthToken(String token) {
+ this.token = token;
+ this.parsed = DgAuthUtil.getJwtPayload(token);
+ }
+
+ @Override
+ public boolean isUserVerified() {
+ return true;
+ }
+
+ @Override
+ public boolean hasFullCapability() {
+ return true;
+ }
+
+ @Override
+ public boolean isAuthenticated() {
+ return true;
+ }
+
+ @Override
+ public Instant getExpiryInstant() {
+ return Instant.ofEpochSecond(Long.parseLong(parsed.getString("exp")));
+ }
+
+ @Override
+ public String getUID() {
+ return parsed.getString("userid");
+ }
+
+ @Override
+ public String getUUID() {
+ return parsed.getString("uuid");
+ }
+
+ @Override
+ public String getUsername() {
+ return parsed.getString("nickname");
+ }
+
+ @Override
+ public String getToken() {
+ return token;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/FailedAuthToken.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/FailedAuthToken.java
new file mode 100644
index 00000000..de5fe2f7
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/FailedAuthToken.java
@@ -0,0 +1,41 @@
+package kr.syeyoung.dungeonsguide.launcher.auth.token;
+
+import java.security.KeyPair;
+import java.time.Instant;
+
+public class FailedAuthToken implements AuthToken {
+ private final Throwable exeption;
+
+ public FailedAuthToken(Throwable exception) {
+ this.exeption = exception;
+ }
+
+ @Override
+ public boolean isUserVerified() {
+ return false;
+ }
+
+ @Override
+ public boolean hasFullCapability() {
+ return false;
+ }
+
+ @Override
+ public boolean isAuthenticated() {
+ return false;
+ }
+
+ @Override
+ public Instant getExpiryInstant() {
+ return Instant.MIN;
+ }
+
+ @Override
+ public String getToken() {
+ return null;
+ }
+
+ public Throwable getException() {
+ return exeption;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/NullToken.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/NullToken.java
new file mode 100644
index 00000000..d2799a02
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/NullToken.java
@@ -0,0 +1,31 @@
+package kr.syeyoung.dungeonsguide.launcher.auth.token;
+
+import java.security.KeyPair;
+import java.time.Instant;
+
+public class NullToken implements AuthToken {
+ @Override
+ public boolean isUserVerified() {
+ return false;
+ }
+
+ @Override
+ public boolean hasFullCapability() {
+ return false;
+ }
+
+ @Override
+ public boolean isAuthenticated() {
+ return false;
+ }
+
+ @Override
+ public Instant getExpiryInstant() {
+ return Instant.MIN;
+ }
+
+ @Override
+ public String getToken() {
+ return null;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/PrivacyPolicyRequiredToken.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/PrivacyPolicyRequiredToken.java
new file mode 100644
index 00000000..62fad840
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/token/PrivacyPolicyRequiredToken.java
@@ -0,0 +1,56 @@
+package kr.syeyoung.dungeonsguide.launcher.auth.token;
+
+import kr.syeyoung.dungeonsguide.launcher.auth.DgAuthUtil;
+import org.json.JSONObject;
+
+import java.time.Instant;
+
+public class PrivacyPolicyRequiredToken implements AuthToken {
+ private String token;
+ private JSONObject parsed;
+
+ public PrivacyPolicyRequiredToken(String token) {
+ this.token = token;
+ this.parsed = DgAuthUtil.getJwtPayload(token);
+ }
+
+ @Override
+ public boolean isUserVerified() {
+ return true;
+ }
+
+ @Override
+ public boolean hasFullCapability() {
+ return false;
+ }
+
+ @Override
+ public boolean isAuthenticated() {
+ return true;
+ }
+
+ @Override
+ public Instant getExpiryInstant() {
+ return Instant.ofEpochSecond(Long.parseLong(parsed.getString("exp")));
+ }
+
+ @Override
+ public String getUID() {
+ return parsed.getString("userid");
+ }
+
+ @Override
+ public String getUUID() {
+ return parsed.getString("uuid");
+ }
+
+ @Override
+ public String getUsername() {
+ return parsed.getString("nickname");
+ }
+
+ @Override
+ public String getToken() {
+ return token;
+ }
+}
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
deleted file mode 100755
index ac30c5e3..00000000
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/Authenticator.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * 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/branch/UpdateRetrieverUtil.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/UpdateRetrieverUtil.java
new file mode 100644
index 00000000..fe7e54dd
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/UpdateRetrieverUtil.java
@@ -0,0 +1,182 @@
+package kr.syeyoung.dungeonsguide.launcher.branch;
+
+import kr.syeyoung.dungeonsguide.launcher.Main;
+import kr.syeyoung.dungeonsguide.launcher.auth.DGResponse;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.AssetNotFoundException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.NoVersionFoundException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.ResponseParsingException;
+import lombok.Builder;
+import lombok.Data;
+import org.apache.commons.io.IOUtils;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import javax.net.ssl.HttpsURLConnection;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.time.Instant;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+public class UpdateRetrieverUtil {
+ private static String getResponse(HttpsURLConnection connection) throws IOException {
+ connection.getResponseCode();
+ InputStream toRead = connection.getErrorStream();
+ if (toRead == null)
+ toRead = connection.getInputStream();
+ String payload = IOUtils.readLines(toRead).stream().collect(Collectors.joining("\n"));
+ return payload;
+ }
+
+ public static List<UpdateBranch> getUpdateBranches() throws IOException {
+ HttpsURLConnection connection = (HttpsURLConnection) new URL(Main.DOMAIN + "/updates/").openConnection();
+ connection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ connection.setRequestProperty("Content-Type", "application/json");
+ connection.setRequestMethod("GET");
+ connection.setDoInput(true);
+ connection.setDoOutput(true);
+
+ JSONArray jsonArray = new JSONArray(getResponse(connection));
+ return jsonArray.toList()
+ .stream()
+ .map(a -> (JSONObject)a)
+ .map(a -> {
+ UpdateBranch updateBranch = new UpdateBranch();
+ updateBranch.setId(a.getLong("id"));
+ updateBranch.setName(a.getString("name"));
+ updateBranch.setMetadata(a.getJSONObject("metadata"));
+ return updateBranch;
+ }).collect(Collectors.toList());
+ }
+
+ public static List<Update> getLatestUpdates(long branchId, int page) throws IOException {
+ HttpsURLConnection connection = (HttpsURLConnection) new URL(Main.DOMAIN + "/updates/"+branchId+"/?page="+page).openConnection();
+ connection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ connection.setRequestMethod("GET");
+ connection.setDoInput(true);
+ connection.setDoOutput(true);
+
+ JSONArray jsonArray = new JSONArray(getResponse(connection));
+ return jsonArray.toList()
+ .stream()
+ .map(a -> (JSONObject)a)
+ .map(a -> {
+ Update update = new Update();
+ update.setId(a.getLong("id"));
+ update.setBranchId(a.getLong("branchId"));
+ update.setName(a.getString("name"));
+ update.setUpdateLog(a.getString("updateLog"));
+ update.setMetadata(a.getJSONObject("metadata"));
+ update.setAssets(a.getJSONObject("assets").getJSONArray("assets").toList().stream().map(b -> (JSONObject)b)
+ .map(b -> {
+ Update.Asset asset = new Update.Asset();
+ asset.setName(b.getString("name"));
+ asset.setAssetId(UUID.fromString(b.getString("assetId")));
+ asset.setSize(b.getLong("size"));
+ asset.setObjectId(b.getString("objectId"));
+ return asset;
+ }).collect(Collectors.toList()));
+ update.setReleaseDate(Instant.parse(a.getString("releaseDate")));
+ return update;
+ }).collect(Collectors.toList());
+ }
+
+ public static Update getUpdate(long branchId, long updateId) throws IOException {
+ HttpsURLConnection connection = (HttpsURLConnection) new URL(Main.DOMAIN + "/updates/"+branchId+"/"+updateId).openConnection();
+ connection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ connection.setRequestMethod("GET");
+ connection.setDoInput(true);
+ connection.setDoOutput(true);
+
+ JSONObject a = new JSONObject(getResponse(connection));
+
+ Update update = new Update();
+ update.setId(a.getLong("id"));
+ update.setBranchId(a.getLong("branchId"));
+ update.setName(a.getString("name"));
+ update.setUpdateLog(a.getString("updateLog"));
+ update.setMetadata(a.getJSONObject("metadata"));
+ update.setAssets(a.getJSONObject("assets").getJSONArray("assets").toList().stream().map(b -> (JSONObject)b)
+ .map(b -> {
+ Update.Asset asset = new Update.Asset();
+ asset.setName(b.getString("name"));
+ asset.setAssetId(UUID.fromString(b.getString("assetId")));
+ asset.setSize(b.getLong("size"));
+ asset.setObjectId(b.getString("objectId"));
+ return asset;
+ }).collect(Collectors.toList()));
+ update.setReleaseDate(Instant.parse(a.getString("releaseDate")));
+ return update;
+ }
+
+ public static InputStream downloadFile(Update update, String assetName) throws IOException {
+ Update.Asset asset = update.getAssets().stream().filter(a -> a.getName().equals(assetName))
+ .findFirst().orElseThrow(() -> new AssetNotFoundException(update.getBranchId()+"", update.getId()+"("+update.getName()+")", assetName));
+
+
+ HttpsURLConnection connection = (HttpsURLConnection) new URL(Main.DOMAIN + "/updates/"+update.getBranchId()+"/"+update.getId()+"/"+asset.getAssetId()).openConnection();
+ connection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ connection.setRequestMethod("GET");
+ connection.setDoInput(true);
+ connection.setDoOutput(true);
+
+ JSONObject result = new JSONObject(getResponse(connection));
+ String url = result.getString("url");
+ String method = result.getString("method");
+
+ connection = (HttpsURLConnection) new URL(url).openConnection();
+ connection.setRequestProperty("User-Agent", "DungeonsGuide/1.0");
+ connection.setRequestMethod(method);
+ return connection.getInputStream();
+ }
+
+ @Data @Builder
+ public static class VersionInfo {
+ String friendlyBranchName = "";
+ long branchId;
+ String friendlyVersionName = "";
+ long updateId;
+ }
+ public static VersionInfo getIds(String branch, String version) throws IOException {
+ long branchId = -1, updateId = -1;
+ UpdateBranch branch1 = null;
+ for (UpdateBranch updateBranch : UpdateRetrieverUtil.getUpdateBranches()) {
+ if (updateBranch.getName().equals(branch) || (branch.equals("$default") &&
+ Optional.ofNullable(updateBranch.getMetadata())
+ .map(a -> a.getJSONObject("additionalMeta"))
+ .map(a -> a.getBoolean("defaultMod")).orElse(false))) {
+ branchId = updateBranch.getId();
+ branch1 = updateBranch;
+ break;
+ }
+ }
+ if (branchId == -1) return null;
+
+ Update target = null;
+ int page = 0;
+ while (updateId == -1) {
+ List<Update> updateList = UpdateRetrieverUtil.getLatestUpdates(branchId, page++);
+ if (updateList == null || updateList.isEmpty()) return null;
+ for (Update update : updateList) {
+ if (update.getName().equals(version) || version.equals("latest")) { // if latest, get the first one.
+ updateId = update.getId();
+ target = update;
+ break;
+ }
+ }
+ }
+
+
+ return VersionInfo.builder()
+ .branchId(branchId)
+ .updateId(updateId)
+ .friendlyBranchName(branch1.getName())
+ .friendlyVersionName(target.getName())
+ .build();
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/AuthChangedEvent.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/AuthChangedEvent.java
new file mode 100644
index 00000000..249c287f
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/events/AuthChangedEvent.java
@@ -0,0 +1,12 @@
+package kr.syeyoung.dungeonsguide.launcher.events;
+
+
+import kr.syeyoung.dungeonsguide.launcher.auth.token.AuthToken;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import net.minecraftforge.fml.common.eventhandler.Event;
+
+@AllArgsConstructor @Getter
+public class AuthChangedEvent extends Event {
+ private AuthToken authToken;
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AssetNotFoundException.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AssetNotFoundException.java
new file mode 100644
index 00000000..d04608e4
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AssetNotFoundException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.Getter;
+
+@Getter
+public class AssetNotFoundException extends RuntimeException {
+ private String branch;
+ private String version;
+ private String asset;
+
+ public AssetNotFoundException(String branch, String version, String asset) {
+ super("No asset found: "+branch+" - "+version+" - "+asset);
+ this.branch = branch;
+ this.version = version;
+ this.asset = asset;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthFailedExeption.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthFailedExeption.java
new file mode 100644
index 00000000..d860afb6
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthFailedExeption.java
@@ -0,0 +1,7 @@
+package kr.syeyoung.dungeonsguide.launcher.exceptions;
+
+public class AuthFailedExeption extends AuthenticationUnavailableException {
+ public AuthFailedExeption(Throwable cause) {
+ super(cause);
+ }
+}
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
index be610c3f..67647b9b 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthServerException.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthServerException.java
@@ -18,17 +18,21 @@
package kr.syeyoung.dungeonsguide.launcher.exceptions;
+import kr.syeyoung.dungeonsguide.launcher.auth.DGResponse;
+import kr.syeyoung.dungeonsguide.launcher.util.QRCodeGenerator;
import org.json.JSONObject;
-public class AuthServerException extends RuntimeException {
- private JSONObject payload;
+import java.util.Objects;
- public AuthServerException(JSONObject payload) {
- super(payload.getString("errorMessage"));
- this.payload = payload;
+public class AuthServerException extends AuthenticationUnavailableException {
+ private DGResponse response;
+
+ public AuthServerException(DGResponse response) {
+ super(response.getErrorMessage() == null ? "Invalid Server Response" : response.getErrorMessage());
+ this.response = response;
}
public String getQRCode() {
- return payload.getString("data");
+ return response.getQrCode() == null ? null : response.getQrCode();
}
}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthenticationUnavailableException.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthenticationUnavailableException.java
new file mode 100644
index 00000000..a42ddd93
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AuthenticationUnavailableException.java
@@ -0,0 +1,12 @@
+package kr.syeyoung.dungeonsguide.launcher.exceptions;
+
+public class AuthenticationUnavailableException extends RuntimeException {
+ public AuthenticationUnavailableException(Throwable cause){
+ super(cause);
+ }
+
+ public AuthenticationUnavailableException(String s) {
+ super(s);
+ }
+ public AuthenticationUnavailableException() {}
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/InvalidSignatureException.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/InvalidSignatureException.java
new file mode 100644
index 00000000..bda43ea4
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/InvalidSignatureException.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.exceptions;
+
+import kr.syeyoung.dungeonsguide.launcher.branch.Update;
+import lombok.Getter;
+
+@Getter
+public class InvalidSignatureException extends RuntimeException {
+ private Update update;
+
+ public InvalidSignatureException(Update update, String message) {
+ super(message);
+ this.update = update;
+ }
+
+ public InvalidSignatureException(Update update, Throwable t) {
+ super(t);
+ this.update = update;
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/NoVersionFoundException.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/NoVersionFoundException.java
new file mode 100644
index 00000000..b99d12c1
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/NoVersionFoundException.java
@@ -0,0 +1,38 @@
+/*
+ * 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.Getter;
+
+@Getter
+public class NoVersionFoundException extends RuntimeException {
+ private String branch;
+ private String version;
+
+ public NoVersionFoundException(String branch, String version) {
+ super("No version found: "+branch+" - "+version);
+ this.branch = branch;
+ this.version = version;
+ }
+ public NoVersionFoundException(String branch, String version, Throwable e) {
+ super("No version found: "+branch+" - "+version, e);
+ this.branch = branch;
+ this.version = version;
+ }
+}
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
index 006e03ca..a48632dc 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/PrivacyPolicyRequiredException.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/PrivacyPolicyRequiredException.java
@@ -18,5 +18,5 @@
package kr.syeyoung.dungeonsguide.launcher.exceptions;
-public class PrivacyPolicyRequiredException extends RuntimeException{
+public class PrivacyPolicyRequiredException extends AuthenticationUnavailableException {
}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/TokenStatus.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/ResponseParsingException.java
index a83818b8..3933b78e 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/authentication/TokenStatus.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/ResponseParsingException.java
@@ -16,12 +16,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-package kr.syeyoung.dungeonsguide.launcher.authentication;
+package kr.syeyoung.dungeonsguide.launcher.exceptions;
+
+import lombok.Getter;
+import org.json.JSONObject;
+
+public class ResponseParsingException extends RuntimeException {
+ @Getter
+ private String payload;
+
+ public ResponseParsingException(String payload, String message) {
+ super(message);
+ this.payload = payload;
+ }
-public enum TokenStatus {
- UNAUTHENTICATED,
- BANNED,
- PP_REQUIRED,
- AUTHENTICATED,
- EXPIRED
}
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
index 2c115e2f..4826b1ba 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/TokenExpiredException.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/TokenExpiredException.java
@@ -18,7 +18,7 @@
package kr.syeyoung.dungeonsguide.launcher.exceptions;
-public class TokenExpiredException extends RuntimeException{
+public class TokenExpiredException extends AuthenticationUnavailableException {
public TokenExpiredException(Throwable parent) {
super(parent);
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiDisplayer.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiDisplayer.java
new file mode 100644
index 00000000..ab772e5d
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiDisplayer.java
@@ -0,0 +1,44 @@
+package kr.syeyoung.dungeonsguide.launcher.gui.screen;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiMainMenu;
+import net.minecraftforge.client.event.GuiOpenEvent;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class GuiDisplayer {
+ public static GuiDisplayer INSTANCE = new GuiDisplayer();
+ private GuiDisplayer() {}
+
+ private Queue<SpecialGuiScreen> guiScreensToShow = new LinkedList<>();
+ private boolean isMcLoaded;
+
+
+ @SubscribeEvent(priority = EventPriority.LOWEST)
+ public void onGuiOpen(GuiOpenEvent guiOpenEvent) {
+ if (guiOpenEvent.gui instanceof GuiMainMenu) {
+ isMcLoaded = true;
+ }
+ if (guiScreensToShow.size() > 0 && guiOpenEvent.gui != guiScreensToShow.peek()) {
+ SpecialGuiScreen gui = guiScreensToShow.peek();
+ if (gui == null) return;
+ gui.setOnDismiss(guiScreensToShow::poll);
+ guiOpenEvent.gui = gui;
+ }
+ }
+
+ public void displayGui(SpecialGuiScreen specialGuiScreen) {
+ if (specialGuiScreen == null) return;
+ if (!guiScreensToShow.contains(specialGuiScreen))
+ guiScreensToShow.add(specialGuiScreen);
+ if (isMcLoaded) {
+ SpecialGuiScreen gui = guiScreensToShow.peek();
+ if (gui == null) return;
+ gui.setOnDismiss(guiScreensToShow::poll);
+ Minecraft.getMinecraft().displayGuiScreen(gui);
+ }
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiLoadingError.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiLoadingError.java
index f699b1d7..436d5d8c 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiLoadingError.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiLoadingError.java
@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-package kr.syeyoung.dungeonsguide.launcher.gui;
+package kr.syeyoung.dungeonsguide.launcher.gui.screen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.*;
@@ -27,18 +27,14 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
-public class GuiLoadingError extends GuiScreen {
- static Throwable cause;
+public class GuiLoadingError extends SpecialGuiScreen {
private final String stacktrace;
- private final GuiScreen originalGUI;
- public GuiLoadingError(GuiScreen originalGUI) {
+ public GuiLoadingError(Throwable cause) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(byteArrayOutputStream);
cause.printStackTrace(printStream);
this.stacktrace = byteArrayOutputStream.toString();
-
- this.originalGUI = originalGUI;
}
@Override
@@ -54,7 +50,7 @@ public class GuiLoadingError extends GuiScreen {
if (button.id == 0) {
FMLCommonHandler.instance().exitJava(-1,true);
} else if (button.id == 1) {
- Minecraft.getMinecraft().displayGuiScreen(originalGUI);
+ dismiss();
}
}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiPrivacyPolicy.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiPrivacyPolicy.java
index b7d21b16..f7acb18a 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/GuiPrivacyPolicy.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiPrivacyPolicy.java
@@ -1,17 +1,12 @@
-package kr.syeyoung.dungeonsguide.launcher.gui;
+package kr.syeyoung.dungeonsguide.launcher.gui.screen;
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 {
+public class GuiPrivacyPolicy extends SpecialGuiScreen {
@Override
public void initGui() {
ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft());
@@ -25,8 +20,9 @@ public class GuiPrivacyPolicy extends GuiScreen {
super.actionPerformed(button);
if (button.id == 0) {
// accept
+ dismiss();
} else if (button.id == 1) {
- Minecraft.getMinecraft().displayGuiScreen(null);
+ dismiss();
}
}
@@ -38,7 +34,7 @@ public class GuiPrivacyPolicy extends GuiScreen {
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);
+ fontRenderer.drawString("Please accept or deny Dungeons Guide Privacy Policy to continue", (sr.getScaledWidth()-fontRenderer.getStringWidth("Please accept or deny Dungeons Guide Privacy Policy to continue"))/2,40,0xFFFF0000);
super.drawScreen(mouseX, mouseY, partialTicks);
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiReferenceLeak.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiReferenceLeak.java
new file mode 100644
index 00000000..9c0a5980
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/GuiReferenceLeak.java
@@ -0,0 +1,90 @@
+/*
+ * 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.screen;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.GuiButton;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraftforge.fml.common.FMLCommonHandler;
+import org.lwjgl.opengl.GL11;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+
+public class GuiReferenceLeak extends SpecialGuiScreen {
+ private final String stacktrace;
+ public GuiReferenceLeak(Throwable cause) {
+
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ PrintStream printStream = new PrintStream(byteArrayOutputStream);
+ cause.printStackTrace(printStream);
+ this.stacktrace = byteArrayOutputStream.toString();
+ }
+
+ @Override
+ public void initGui() {
+ ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft());
+ this.buttonList.add(new GuiButton(0, sr.getScaledWidth()/2-100,sr.getScaledHeight()-70 ,"Close Minecraft"));
+ this.buttonList.add(new GuiButton(1, sr.getScaledWidth()/2-100,sr.getScaledHeight()-40 ,"Play With DG in Inconsistent State"));
+ }
+
+ @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) {
+ dismiss();
+ }
+ }
+
+ @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 error while reloading", (sr.getScaledWidth()-fontRenderer.getStringWidth("DungeonsGuide has ran into error while reloading"))/2,40,0xFFFF0000);
+ fontRenderer.drawString("Please contact DungeonsGuide support with this screen", (sr.getScaledWidth()-fontRenderer.getStringWidth("Please contact developer with this screen"))/2, (int) (40+fontRenderer.FONT_HEIGHT*1.5),0xFFFF0000);
+ fontRenderer.drawString("Playing in this state is VERY UNSUGGESTED. Undesired behaviors might occur.", (sr.getScaledWidth()-fontRenderer.getStringWidth("Playing in this state is VERY UNSUGGESTED. Undesired behaviors might occur."))/2, (int) (40+fontRenderer.FONT_HEIGHT*3),0xFFFF0000);
+
+ int tenth = sr.getScaledWidth() / 10;
+
+ Gui.drawRect(tenth, 90,sr.getScaledWidth()-tenth, sr.getScaledHeight()-80, 0xFF5B5B5B);
+ String[] split = stacktrace.split("\n");
+ clip(sr, tenth, 90,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 + 92, 0xFFFFFFFF);
+ }
+ GL11.glDisable(GL11.GL_SCISSOR_TEST);
+
+ 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/screen/SpecialGuiScreen.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/SpecialGuiScreen.java
new file mode 100644
index 00000000..0a9f2602
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/gui/screen/SpecialGuiScreen.java
@@ -0,0 +1,17 @@
+package kr.syeyoung.dungeonsguide.launcher.gui.screen;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiScreen;
+
+public abstract class SpecialGuiScreen extends GuiScreen {
+
+ private Runnable onDismiss;
+ public void setOnDismiss(Runnable dismissed) {
+ this.onDismiss = dismissed;
+ }
+
+ protected void dismiss() {
+ onDismiss.run();
+ Minecraft.getMinecraft().displayGuiScreen(null);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/ByteStreamURLHandler.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/ByteStreamURLHandler.java
new file mode 100644
index 00000000..d0859076
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/ByteStreamURLHandler.java
@@ -0,0 +1,46 @@
+package kr.syeyoung.dungeonsguide.launcher.loader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+public class ByteStreamURLHandler extends URLStreamHandler {
+ private InputStreamGenerator converter;
+ public ByteStreamURLHandler(InputStreamGenerator converter) {
+ this.converter = converter;
+ }
+ public interface InputStreamGenerator {
+ InputStream convert(String name);
+ }
+
+ public class ByteStreamURLConnection extends URLConnection {
+
+ /**
+ * Constructs a URL connection to the specified URL. A connection to
+ * the object referenced by the URL is not created.
+ *
+ * @param url the specified URL.
+ */
+ protected ByteStreamURLConnection(URL url) {
+ super(url);
+ connected = false;
+ }
+
+ @Override
+ public void connect() throws IOException {
+ connected = true;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return converter.convert(url.getPath());
+ }
+ }
+
+ @Override
+ protected URLConnection openConnection(URL u) throws IOException {
+ return new ByteStreamURLConnection(u);
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/DGClassLoader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/DGClassLoader.java
new file mode 100644
index 00000000..1fbc39b0
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/DGClassLoader.java
@@ -0,0 +1,81 @@
+package kr.syeyoung.dungeonsguide.launcher.loader;
+
+import sun.misc.Resource;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public abstract class DGClassLoader extends ClassLoader implements ByteStreamURLHandler.InputStreamGenerator{
+ public DGClassLoader(ClassLoader parent) {
+ super(parent);
+ }
+
+ public Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ synchronized (getClassLoadingLock(name)) {
+ // First, check if the class has already been loaded
+ 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;
+ }
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ byte[] res;
+ try {
+ res = getClassBytes(name);
+ } catch (IOException e) {
+ throw new ClassNotFoundException(name, e);
+ }
+ if (res != null) {
+ return defineClass(name, res, 0, res.length);
+ } else {
+ throw new ClassNotFoundException(name);
+ }
+ }
+
+ public abstract byte[] getClassBytes(String name) throws IOException;
+
+ public URL getResource(String name) {
+ URL url = findResource(name);
+ if (url == null && getParent() != null ) {
+ url = getParent().getResource(name);
+ }
+ return url;
+ }
+
+ private ByteStreamURLHandler urlHandler = new ByteStreamURLHandler(this);
+ @Override
+ public URL findResource(String name) {
+ try {
+ return new URL("dungeonsguide", "",0, name, urlHandler);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
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
index 53d9b70d..a79fdb7e 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/IDGLoader.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/IDGLoader.java
@@ -19,21 +19,19 @@
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;
+import java.io.IOException;
public interface IDGLoader {
- void loadJar(Authenticator authenticator) throws InstantiationException, IllegalAccessException, ClassNotFoundException;
+ DGInterface loadDungeonsGuide() throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException;
DGInterface getInstance();
- void unloadJar() throws ReferenceLeakedException;
+ void unloadDungeonsGuide() throws ReferenceLeakedException;
boolean isUnloadable();
boolean isLoaded();
- String strategyName();
+ String loaderName();
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
index 0b6cf124..fa86054d 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java
@@ -20,9 +20,13 @@ 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 org.apache.commons.io.IOUtils;
+import sun.misc.Resource;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
@@ -30,6 +34,9 @@ import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
public class JarLoader implements IDGLoader {
private DGInterface dgInterface;
@@ -38,61 +45,42 @@ public class JarLoader implements IDGLoader {
private boolean loaded;
- public static class JarClassLoader extends URLClassLoader {
- public JarClassLoader(URL[] urls, ClassLoader parent) {
- super(urls, parent);
- }
+ public static class JarClassLoader extends DGClassLoader {
+ public JarClassLoader(ClassLoader parent, ZipInputStream zipInputStream) throws IOException {
+ super(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;
+ ZipEntry zipEntry;
+ while ((zipEntry=zipInputStream.getNextEntry()) != null) {
+ this.loadedResources.put(zipEntry.getName(), IOUtils.toByteArray(zipInputStream));
}
+
+ zipInputStream.close();
+ }
+ private final HashMap<String, byte[]> loadedResources = new HashMap<String, byte[]>();
+ @Override
+ public byte[] getClassBytes(String name) throws IOException { // . separated.
+ return this.loadedResources.get(name.replace(".", "/"));
}
- public Class<?> loadClassResolve(String name, boolean resolve) throws ClassNotFoundException {
- return this.loadClass(name, resolve);
+ @Override
+ public InputStream convert(String name) { // / separated
+ if (this.loadedResources.containsKey(name.substring(1)))
+ return new ByteArrayInputStream(this.loadedResources.get(name.substring(1)));
+ return null;
}
}
private JarClassLoader classLoader;
@Override
- public void loadJar(Authenticator authenticator) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ public DGInterface loadDungeonsGuide() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
if (dgInterface != null) throw new IllegalStateException("Already loaded");
- classLoader = new JarClassLoader(new URL[] {
- Main.class.getResource("/mod.jar")
- }, this.getClass().getClassLoader());
+ classLoader = new JarClassLoader(this.getClass().getClassLoader(), new ZipInputStream(JarLoader.class.getResourceAsStream("/mod.jar")));
- dgInterface = (DGInterface) classLoader.loadClassResolve("kr.syeyoung.dungeonsguide.DungeonsGuide", true).newInstance();
+ dgInterface = (DGInterface) classLoader.loadClass("kr.syeyoung.dungeonsguide.DungeonsGuide", true).newInstance();
phantomReference = new PhantomReference<>(classLoader, refQueue);
+ return dgInterface;
}
@Override
@@ -101,7 +89,7 @@ public class JarLoader implements IDGLoader {
}
@Override
- public void unloadJar() throws ReferenceLeakedException {
+ public void unloadDungeonsGuide() throws ReferenceLeakedException {
classLoader = null;
dgInterface.unload();
dgInterface = null;
@@ -123,7 +111,7 @@ public class JarLoader implements IDGLoader {
}
@Override
- public String strategyName() {
+ public String loaderName() {
return "jar";
}
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
index 1338138d..683f77d0 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java
@@ -19,18 +19,55 @@
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 org.apache.commons.io.IOUtils;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.io.InputStream;
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.util.HashMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
public class LocalLoader implements IDGLoader {
private DGInterface dgInterface;
+ private ReferenceQueue<ClassLoader> refQueue = new ReferenceQueue<>();
+ private PhantomReference<ClassLoader> phantomReference;
+
+ private boolean loaded;
+
+ public static class LocalClassLoader extends DGClassLoader {
+ public LocalClassLoader(ClassLoader parent) throws IOException {
+ super(parent);
+ }
+ @Override
+ public byte[] getClassBytes(String name) throws IOException { // . separated.
+ InputStream in = convert("/"+name.replace(".", "/"));
+ if (in == null) return null;
+ return IOUtils.toByteArray(in);
+ }
+
+ @Override
+ public InputStream convert(String name) { // / separated
+ return LocalLoader.class.getResourceAsStream(name);
+ }
+ }
+
+ private LocalClassLoader classLoader;
+
@Override
- public void loadJar(Authenticator authenticator) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ public DGInterface loadDungeonsGuide() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
if (dgInterface != null) throw new IllegalStateException("Already loaded");
- dgInterface = (DGInterface) Class.forName("kr.syeyoung.dungeonsguide.DungeonsGuide").newInstance();
+
+ classLoader = new LocalClassLoader(this.getClass().getClassLoader());
+
+ dgInterface = (DGInterface) classLoader.loadClass("kr.syeyoung.dungeonsguide.DungeonsGuide", true).newInstance();
+ phantomReference = new PhantomReference<>(classLoader, refQueue);
+ return dgInterface;
}
@Override
@@ -39,12 +76,20 @@ public class LocalLoader implements IDGLoader {
}
@Override
- public void unloadJar() throws ReferenceLeakedException {
- throw new UnsupportedOperationException();
+ public void unloadDungeonsGuide() 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 false;
+ return true;
}
@Override
@@ -53,7 +98,7 @@ public class LocalLoader implements IDGLoader {
}
@Override
- public String strategyName() {
+ public String loaderName() {
return "local";
}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java
new file mode 100644
index 00000000..719f5cce
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java
@@ -0,0 +1,152 @@
+/*
+ * 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.branch.Update;
+import kr.syeyoung.dungeonsguide.launcher.branch.UpdateBranch;
+import kr.syeyoung.dungeonsguide.launcher.branch.UpdateRetrieverUtil;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.InvalidSignatureException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.NoVersionFoundException;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.ReferenceLeakedException;
+import org.apache.commons.io.IOUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.util.HashMap;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class RemoteLoader implements IDGLoader {
+ private DGInterface dgInterface;
+ private ReferenceQueue<ClassLoader> refQueue = new ReferenceQueue<>();
+ private PhantomReference<ClassLoader> phantomReference;
+
+ private boolean loaded;
+
+ public RemoteLoader(String friendlyBranchName, long branchId, long updateId) {
+ this.friendlyBranchName = friendlyBranchName;
+ this.branchId = branchId;
+ this.updateId = updateId;
+ }
+
+
+ public static class JarClassLoader extends DGClassLoader {
+ public JarClassLoader(ClassLoader parent, ZipInputStream zipInputStream) throws IOException {
+ super(parent);
+
+ ZipEntry zipEntry;
+ while ((zipEntry=zipInputStream.getNextEntry()) != null) {
+ this.loadedResources.put(zipEntry.getName(), IOUtils.toByteArray(zipInputStream));
+ }
+
+ zipInputStream.close();
+ }
+ private final HashMap<String, byte[]> loadedResources = new HashMap<String, byte[]>();
+ @Override
+ public byte[] getClassBytes(String name) throws IOException { // . separated.
+ return this.loadedResources.get(name.replace(".", "/"));
+ }
+
+ @Override
+ public InputStream convert(String name) { // / separated
+ if (this.loadedResources.containsKey(name.substring(1)))
+ return new ByteArrayInputStream(this.loadedResources.get(name.substring(1)));
+ return null;
+ }
+ }
+
+ private JarClassLoader classLoader;
+
+
+
+ @Override
+ public DGInterface loadDungeonsGuide() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
+ if (dgInterface != null) throw new IllegalStateException("Already loaded");
+
+ Update target = UpdateRetrieverUtil.getUpdate(branchId, updateId);
+ friendlyVersionName = target.getName();
+
+ InputStream in;
+ byte[] mod = IOUtils.toByteArray(in = UpdateRetrieverUtil.downloadFile(target, "mod.jar"));
+ in.close();
+ byte[] signature =IOUtils.toByteArray(in = UpdateRetrieverUtil.downloadFile(target, "signature.asc"));
+ in.close();
+ int version = target.getMetadata().getInt("signatureVersion");
+
+ if (version == 0) {
+ SignatureValidator.validateVersion1Signature(target, mod, signature);
+ } else {
+ throw new InvalidSignatureException(target, "Invalid Signature Version: "+version);
+ }
+
+ classLoader = new JarClassLoader(this.getClass().getClassLoader(), new ZipInputStream(new ByteArrayInputStream(mod)));
+
+ dgInterface = (DGInterface) classLoader.loadClass("kr.syeyoung.dungeonsguide.DungeonsGuide", true).newInstance();
+ phantomReference = new PhantomReference<>(classLoader, refQueue);
+ return dgInterface;
+ }
+
+ @Override
+ public DGInterface getInstance() {
+ return dgInterface;
+ }
+
+ @Override
+ public void unloadDungeonsGuide() 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 loaderName() {
+ return "remote";
+ }
+
+ private long branchId = -1; // pre-retrieved
+ private long updateId = -1; // pre-retrieved
+
+ private String friendlyBranchName = "";
+ private String friendlyVersionName = "";
+ @Override
+ public String version() {
+ return friendlyBranchName+"("+branchId+")@"+friendlyVersionName+"("+updateId+")"; // maybe read the thing...
+ }
+}
diff --git a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/SignatureValidator.java b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/SignatureValidator.java
new file mode 100644
index 00000000..7a8ce832
--- /dev/null
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/SignatureValidator.java
@@ -0,0 +1,43 @@
+package kr.syeyoung.dungeonsguide.launcher.loader;
+
+import kr.syeyoung.dungeonsguide.launcher.branch.Update;
+import kr.syeyoung.dungeonsguide.launcher.exceptions.InvalidSignatureException;
+import org.apache.commons.codec.binary.Base64;
+
+import java.security.*;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+public class SignatureValidator {
+ private static PublicKey dgPublicKey;
+ private static 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);
+ }
+
+ public static void validateVersion1Signature(Update update, byte[] payload, byte[] signature) {
+ try {
+ Signature sign = Signature.getInstance("SHA512withRSA");
+ sign.initVerify(getDGPublicKey());
+ sign.update(payload);
+ boolean truth = sign.verify(signature);
+ if (!truth) throw new InvalidSignatureException(update, "DG SIGNATURE FORGED");
+ }catch (NoSuchAlgorithmException | InvalidKeySpecException | SignatureException | InvalidKeyException e) {
+ throw new InvalidSignatureException(update, e);
+ }
+ }
+
+
+}
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
deleted file mode 100755
index 5a310738..00000000
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGConnection.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.auth.ResourceManager;
-
-import java.io.ByteArrayInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLConnection;
-
-public class DGConnection extends URLConnection {
-
- protected DGConnection(URL url) {
- super(url);
- connected = false;
- }
-
- @Override
- public void connect() throws IOException {
- }
- @Override
- public InputStream getInputStream() throws IOException {
- if (ResourceManager.getInstance().getResources() != null) {
- String path = url.getPath().substring(1);
- if (!ResourceManager.getInstance().getResources().containsKey(path)) throw new FileNotFoundException();
- return new ByteArrayInputStream(ResourceManager.getInstance().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
deleted file mode 100755
index 456e7f8c..00000000
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandler.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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 lombok.AllArgsConstructor;
-
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLStreamHandler;
-
-@AllArgsConstructor
-public class DGStreamHandler extends URLStreamHandler {
- @Override
- protected URLConnection openConnection(URL url) throws IOException {
- return new DGConnection(url);
- }
-}
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
deleted file mode 100755
index a7eefa37..00000000
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/url/DGStreamHandlerFactory.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 lombok.AllArgsConstructor;
-
-import java.net.URLStreamHandler;
-import java.net.URLStreamHandlerFactory;
-
-@AllArgsConstructor
-public class DGStreamHandlerFactory implements URLStreamHandlerFactory {
- @Override
- public URLStreamHandler createURLStreamHandler(String protocol) {
- if ("z".equals(protocol)) {
- return new DGStreamHandler();
- }
- return null;
- }
-}