aboutsummaryrefslogtreecommitdiff
path: root/loader/src/main/java/kr/syeyoung/dungeonsguide
diff options
context:
space:
mode:
authorsyeyoung <cyoung06@naver.com>2022-11-16 17:45:55 +0900
committersyeyoung <cyoung06@naver.com>2022-11-16 17:59:56 +0900
commit60032db80aefe4a9f58b354b5a26938ed76f5c46 (patch)
tree17a7b6e873db2ea3c40aef508134842ce69c5991 /loader/src/main/java/kr/syeyoung/dungeonsguide
parent3708965c0c22c216336f8aa28158ba22bfc03b60 (diff)
parent241893934ef119566693165589fce0921c35e4af (diff)
downloadSkyblock-Dungeons-Guide-60032db80aefe4a9f58b354b5a26938ed76f5c46.tar.gz
Skyblock-Dungeons-Guide-60032db80aefe4a9f58b354b5a26938ed76f5c46.tar.bz2
Skyblock-Dungeons-Guide-60032db80aefe4a9f58b354b5a26938ed76f5c46.zip
- Update Downloading and Signature Verification
Signed-off-by: syeyoung <cyoung06@naver.com>
Diffstat (limited to 'loader/src/main/java/kr/syeyoung/dungeonsguide')
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/DungeonsGuideReloadListener.java3
-rwxr-xr-xloader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java19
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/AuthManager.java11
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/DgAuthUtil.java15
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/branch/UpdateRetrieverUtil.java182
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/AssetNotFoundException.java35
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/InvalidSignatureException.java37
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/exceptions/NoVersionFoundException.java38
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/ByteStreamURLHandler.java46
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/DGClassLoader.java81
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/IDGLoader.java4
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/JarLoader.java70
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/LocalLoader.java54
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java155
-rw-r--r--loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/SignatureValidator.java43
15 files changed, 681 insertions, 112 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 0ec65780..e8d3e36b 100755
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/Main.java
@@ -19,7 +19,9 @@
package kr.syeyoung.dungeonsguide.launcher;
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.NoVersionFoundException;
import kr.syeyoung.dungeonsguide.launcher.exceptions.ReferenceLeakedException;
import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiDisplayer;
import kr.syeyoung.dungeonsguide.launcher.gui.screen.GuiLoadingError;
@@ -28,6 +30,7 @@ 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.resources.IReloadableResourceManager;
import net.minecraftforge.common.MinecraftForge;
@@ -96,7 +99,7 @@ public class Main
}
currentLoader = null;
}
- private void load(IDGLoader newLoader) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ private void load(IDGLoader newLoader) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
if (dgInterface != null) throw new IllegalStateException("DG is loaded");
dgInterface = newLoader.loadDungeonsGuide();
currentLoader = newLoader;
@@ -157,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());
}
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 1613e687..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
@@ -3,6 +3,7 @@ package kr.syeyoung.dungeonsguide.launcher.auth;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.mojang.authlib.exceptions.AuthenticationException;
+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;
@@ -34,10 +35,6 @@ public class AuthManager {
if(INSTANCE == null) INSTANCE = new AuthManager();
return INSTANCE;
}
-
- @Setter
- private String baseserverurl = "https://dungeons.guide";
-
private AuthToken currentToken = new NullToken();
public AuthToken getToken() {
@@ -98,9 +95,9 @@ public class AuthManager {
reauthLock = true;
try {
- String token = DgAuthUtil.requestAuth(baseserverurl);
+ String token = DgAuthUtil.requestAuth();
byte[] encSecret = DgAuthUtil.checkSessionAuthenticityAndReturnEncryptedSecret(token);
- currentToken = DgAuthUtil.verifyAuth(token, encSecret, baseserverurl);
+ currentToken = DgAuthUtil.verifyAuth(token, encSecret);
MinecraftForge.EVENT_BUS.post(new AuthChangedEvent(currentToken));
if (currentToken instanceof PrivacyPolicyRequiredToken) {
@@ -130,7 +127,7 @@ public class AuthManager {
if (currentToken instanceof PrivacyPolicyRequiredToken) {
reauthLock = true;
try {
- currentToken = DgAuthUtil.acceptNewPrivacyPolicy(currentToken.getToken(), baseserverurl);
+ currentToken = DgAuthUtil.acceptNewPrivacyPolicy(currentToken.getToken());
if (currentToken instanceof PrivacyPolicyRequiredToken) throw new PrivacyPolicyRequiredException();
} catch (IOException e) {
currentToken = new FailedAuthToken(e);
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
index f585ac20..78fc91c9 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/DgAuthUtil.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/auth/DgAuthUtil.java
@@ -5,6 +5,7 @@ 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;
@@ -52,14 +53,16 @@ public class DgAuthUtil {
);
} catch (Exception e) {
throw new ResponseParsingException(payload, e.getMessage());
+ } finally {
+ toRead.close();
}
}
- public static String requestAuth(String baseurl) throws IOException {
+ public static String requestAuth() throws IOException {
GameProfile profile = Minecraft.getMinecraft().getSession().getProfile();
- HttpsURLConnection connection = (HttpsURLConnection) new URL(baseurl + "/auth/requestAuth").openConnection();
+ 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");
@@ -96,8 +99,8 @@ public class DgAuthUtil {
return result;
}
- public static AuthToken verifyAuth(String tempToken, byte[] encSecret, String baseurl) throws IOException {
- HttpsURLConnection urlConnection = (HttpsURLConnection) new URL(baseurl + "/auth/authenticate").openConnection();
+ 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");
@@ -120,8 +123,8 @@ public class DgAuthUtil {
}
}
- public static AuthToken acceptNewPrivacyPolicy(String tempToken, String baseurl) throws IOException {
- HttpsURLConnection urlConnection = (HttpsURLConnection) new URL(baseurl + "/auth/acceptPrivacyPolicy").openConnection();
+ 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");
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/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/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/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 f5149f82..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
@@ -21,8 +21,10 @@ package kr.syeyoung.dungeonsguide.launcher.loader;
import kr.syeyoung.dungeonsguide.launcher.DGInterface;
import kr.syeyoung.dungeonsguide.launcher.exceptions.ReferenceLeakedException;
+import java.io.IOException;
+
public interface IDGLoader {
- DGInterface loadDungeonsGuide() throws InstantiationException, IllegalAccessException, ClassNotFoundException;
+ DGInterface loadDungeonsGuide() throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException;
DGInterface getInstance();
void unloadDungeonsGuide() throws ReferenceLeakedException;
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 6baee0f3..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
@@ -21,7 +21,12 @@ package kr.syeyoung.dungeonsguide.launcher.loader;
import kr.syeyoung.dungeonsguide.launcher.DGInterface;
import kr.syeyoung.dungeonsguide.launcher.Main;
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;
@@ -29,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;
@@ -37,60 +45,40 @@ 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 DGInterface loadDungeonsGuide() 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;
}
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 ef9b3e7a..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
@@ -20,16 +20,54 @@ package kr.syeyoung.dungeonsguide.launcher.loader;
import kr.syeyoung.dungeonsguide.launcher.DGInterface;
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 DGInterface loadDungeonsGuide() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ public DGInterface loadDungeonsGuide() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
if (dgInterface != null) throw new IllegalStateException("Already loaded");
- return 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,11 +77,19 @@ public class LocalLoader implements IDGLoader {
@Override
public void unloadDungeonsGuide() throws ReferenceLeakedException {
- throw new UnsupportedOperationException();
+ 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
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
index 98155245..719f5cce 100644
--- a/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java
+++ b/loader/src/main/java/kr/syeyoung/dungeonsguide/launcher/loader/RemoteLoader.java
@@ -1,99 +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.net.URL;
-import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
public class RemoteLoader implements IDGLoader {
- private String branch;
- private String version;
-
-
+ private DGInterface dgInterface;
private ReferenceQueue<ClassLoader> refQueue = new ReferenceQueue<>();
private PhantomReference<ClassLoader> phantomReference;
private boolean loaded;
- public static class JarClassLoader extends URLClassLoader {
- public JarClassLoader(URL[] urls, ClassLoader parent) {
- super(urls, parent);
- }
+ public RemoteLoader(String friendlyBranchName, long branchId, long updateId) {
+ this.friendlyBranchName = friendlyBranchName;
+ this.branchId = branchId;
+ this.updateId = updateId;
+ }
- @Override
- protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
-
- synchronized (getClassLoadingLock(name)) {
- Class<?> c = findLoadedClass(name);
- if (c == null) {
-
- try {
- if (c == null) {
- long t0 = System.nanoTime();
- c = findClass(name);
-
- sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t0);
- sun.misc.PerfCounter.getFindClasses().increment();
- }
- } catch (ClassNotFoundException e) {
- // ClassNotFoundException thrown if class not found
- // from the non-null parent class loader
- }
- if (getParent() != null && c == null) {
- long t0 = System.nanoTime();
- c = getParent().loadClass(name);
- long t1 = System.nanoTime();
- sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
- }
- }
- if (resolve) {
- resolveClass(c);
- }
- return c;
+
+ public 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(".", "/"));
}
- 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 JarLoader.JarClassLoader classLoader;
+ private JarClassLoader classLoader;
+
+
+
@Override
- public DGInterface loadDungeonsGuide() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
- return null;
+ 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 null;
+ 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 false;
+ return true;
}
@Override
public boolean isLoaded() {
- return false;
+ return dgInterface != null;
}
@Override
public String loaderName() {
- return branch;
+ 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 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);
+ }
+ }
+
+
+}