diff options
Diffstat (limited to 'loader/src/main')
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; - } -} |