From 8f7340fbc8f84ec35ff9bfc07826b438480ed344 Mon Sep 17 00:00:00 2001
From: Gravita <gravita@gravit.pro>
Date: Fri, 10 Jun 2022 16:38:23 +0300
Subject: [PATCH] Use Launcher API

---
 build.gradle                                  |  4 ++
 proxy/build.gradle                            |  3 +
 .../client/InitialLoginSessionHandler.java    | 67 +++++++++----------
 .../proxy/connection/util/LauncherUtil.java   | 57 ++++++++++++++++
 4 files changed, 95 insertions(+), 36 deletions(-)
 create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/connection/util/LauncherUtil.java

diff --git a/build.gradle b/build.gradle
index 2281d1a..dfd2c94 100644
--- a/build.gradle
+++ b/build.gradle
@@ -45,6 +45,10 @@ allprojects {
             url 'https://oss.sonatype.org/content/groups/public/'
         }
 
+        maven {
+            url 'https://oss.sonatype.org/content/repositories/snapshots'
+        }
+
         // Velocity repo
         maven {
             url "https://nexus.velocitypowered.com/repository/maven-public/"
diff --git a/proxy/build.gradle b/proxy/build.gradle
index b486548..2a7184a 100644
--- a/proxy/build.gradle
+++ b/proxy/build.gradle
@@ -45,6 +45,9 @@ dependencies {
     implementation project(':velocity-api').sourceSets.ap.output
     implementation project(':velocity-native')
 
+    compileOnly "pro.gravit.launcher:launcher-core:5.2.12"
+    compileOnly "pro.gravit.launcher:launcher-ws-api:5.2.12"
+
     implementation "io.netty:netty-codec:${nettyVersion}"
     implementation "io.netty:netty-codec-haproxy:${nettyVersion}"
     implementation "io.netty:netty-codec-http:${nettyVersion}"
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java
index 3a4cae8..d6af30c 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java
@@ -33,12 +33,15 @@ import com.velocitypowered.api.util.GameProfile;
 import com.velocitypowered.proxy.VelocityServer;
 import com.velocitypowered.proxy.connection.MinecraftConnection;
 import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
+import com.velocitypowered.proxy.connection.util.LauncherUtil;
 import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
 import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
 import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
 import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
 import com.velocitypowered.proxy.protocol.packet.ServerLogin;
 import io.netty.buffer.ByteBuf;
+
+import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
@@ -54,6 +57,9 @@ import org.apache.logging.log4j.Logger;
 import org.asynchttpclient.ListenableFuture;
 import org.asynchttpclient.Response;
 import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import pro.gravit.launcher.request.Request;
+import pro.gravit.launcher.request.RequestException;
+import pro.gravit.launcher.request.auth.CheckServerRequest;
 
 public class InitialLoginSessionHandler implements MinecraftSessionHandler {
 
@@ -183,20 +189,17 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
       byte[] decryptedSharedSecret = decryptRsa(serverKeyPair, packet.getSharedSecret());
       String serverId = generateServerId(decryptedSharedSecret, serverKeyPair.getPublic());
 
-      String playerIp = ((InetSocketAddress) mcConnection.getRemoteAddress()).getHostString();
-      String url = String.format(MOJANG_HASJOINED_URL,
-          urlFormParameterEscaper().escape(login.getUsername()), serverId);
-
-      if (server.getConfiguration().shouldPreventClientProxyConnections()) {
-        url += "&ip=" + urlFormParameterEscaper().escape(playerIp);
-      }
 
-      ListenableFuture<Response> hasJoinedResponse = server.getAsyncHttpClient().prepareGet(url)
-          .execute();
-      hasJoinedResponse.addListener(() -> {
+      Request.getRequestService().request(new CheckServerRequest(login.getUsername(), serverId)).handleAsync((response, exception) -> {
         if (mcConnection.isClosed()) {
           // The player disconnected after we authenticated them.
-          return;
+          return null;
+        }
+
+        if(exception != null) {
+          if(exception instanceof ExecutionException) {
+            exception = exception.getCause();
+          }
         }
 
         // Go ahead and enable encryption. Once the client sends EncryptionResponse, encryption
@@ -208,38 +211,30 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
           // At this point, the connection is encrypted, but something's wrong on our side and
           // we can't do anything about it.
           mcConnection.close(true);
-          return;
+          return null;
         }
-
-        try {
-          Response profileResponse = hasJoinedResponse.get();
-          if (profileResponse.getStatusCode() == 200) {
-            // All went well, initialize the session.
-            mcConnection.setSessionHandler(new AuthSessionHandler(
-                server, inbound, GENERAL_GSON.fromJson(profileResponse.getResponseBody(), GameProfile.class), true
-            ));
-          } else if (profileResponse.getStatusCode() == 204) {
-            // Apparently an offline-mode user logged onto this online-mode proxy.
-            inbound.disconnect(Component.translatable("velocity.error.online-mode-only",
-                NamedTextColor.RED));
-          } else {
-            // Something else went wrong
-            logger.error(
-                "Got an unexpected error code {} whilst contacting Mojang to log in {} ({})",
-                profileResponse.getStatusCode(), login.getUsername(), playerIp);
-            inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
-          }
-        } catch (ExecutionException e) {
-          logger.error("Unable to authenticate with Mojang", e);
+        if (exception == null) {
+          // All went well, initialize the session.
+          mcConnection.setSessionHandler(new AuthSessionHandler(
+                  server, inbound, LauncherUtil.makeGameProfile(response), true
+          ));
+        } else if (exception instanceof RequestException) {
+          // Apparently an offline-mode user logged onto this online-mode proxy.
+          inbound.disconnect(Component.translatable("velocity.error.online-mode-only",
+                  NamedTextColor.RED));
+        } else {
+          // Something else went wrong
+          logger.error("Unable to authenticate with Launcher", exception);
           inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
-        } catch (InterruptedException e) {
-          // not much we can do usefully
-          Thread.currentThread().interrupt();
         }
+        return null;
       }, mcConnection.eventLoop());
     } catch (GeneralSecurityException e) {
       logger.error("Unable to enable encryption", e);
       mcConnection.close(true);
+    } catch (IOException e) {
+      logger.error("Unable to authenticate with Launcher", e);
+      inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
     }
     return true;
   }
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/LauncherUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/LauncherUtil.java
new file mode 100644
index 0000000..6b06f7f
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/LauncherUtil.java
@@ -0,0 +1,57 @@
+package com.velocitypowered.proxy.connection.util;
+
+import com.velocitypowered.api.util.GameProfile;
+import pro.gravit.launcher.Launcher;
+import pro.gravit.launcher.events.request.CheckServerRequestEvent;
+import pro.gravit.launcher.profiles.PlayerProfile;
+import pro.gravit.launcher.profiles.Texture;
+import pro.gravit.utils.helper.SecurityHelper;
+
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+public class LauncherUtil {
+    public static GameProfile makeGameProfile(CheckServerRequestEvent event) {
+        PlayerProfile profile = event.playerProfile;
+        List<GameProfile.Property> properties = new ArrayList<>();
+        for(var e : profile.properties.entrySet()) {
+            properties.add(new GameProfile.Property(e.getKey(), e.getValue(), ""));
+        }
+        {
+            String key = "textures";
+            GameProfileTextureProperties textureProperty = new GameProfileTextureProperties();
+            textureProperty.profileId = event.playerProfile.uuid.toString().replace("-", "");
+            textureProperty.profileName = event.playerProfile.username;
+            textureProperty.timestamp = System.currentTimeMillis();
+            for(var texture : profile.assets.entrySet()) {
+                textureProperty.textures.put(texture.getKey(), new GameProfileTextureProperties.GameTexture(texture.getValue()));
+            }
+            String value = Base64.getEncoder().encodeToString(Launcher.gsonManager.gson.toJson(textureProperty).getBytes(StandardCharsets.UTF_8));
+            properties.add(new GameProfile.Property(key, value, ""));
+        }
+        return new GameProfile(profile.uuid, profile.username, properties);
+    }
+
+    public static class GameProfileTextureProperties {
+        public long timestamp;
+        public String profileId;
+        public String profileName;
+        public Map<String, GameTexture> textures = new HashMap<>();
+        public static class GameTexture {
+            public String url;
+            public String digest;
+            public Map<String, String> metadata;
+
+            public GameTexture(String url, String digest, Map<String, String> metadata) {
+                this.url = url;
+                this.digest = digest;
+                this.metadata = metadata;
+            }
+
+            public GameTexture(Texture texture) {
+                this.url = texture.url;
+                this.digest = texture.digest == null ? null : SecurityHelper.toHex(texture.digest);
+            }
+        }
+    }
+}
-- 
2.36.1

