From 0f52dbaaf80fe04e7d0aa4386bad667a4f5270db Mon Sep 17 00:00:00 2001 From: MiniDay <372403923@qq.com> Date: Tue, 28 Jan 2025 19:20:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=AD=94=E6=94=B9=E7=BD=91=E6=98=93?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../velocitypowered/api/util/GameProfile.java | 6 +- .../velocitypowered/proxy/VelocityServer.java | 11 +- .../client/InitialLoginSessionHandler.java | 98 ++++++++----- .../proxy/netease/AuthResponse.java | 133 ++++++++++++++++++ .../proxy/netease/NeteaseDataManager.java | 80 +++++++++++ 5 files changed, 289 insertions(+), 39 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/netease/AuthResponse.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/netease/NeteaseDataManager.java diff --git a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java index 27c42138..b45d1268 100644 --- a/api/src/main/java/com/velocitypowered/api/util/GameProfile.java +++ b/api/src/main/java/com/velocitypowered/api/util/GameProfile.java @@ -19,7 +19,7 @@ public final class GameProfile { private final UUID id; private final String undashedId; - private final String name; + private String name; private final List properties; /** @@ -80,6 +80,10 @@ public final class GameProfile { return name; } + public void setName(String name) { + this.name = name; + } + /** * Returns an immutable list of profile properties associated with this profile. * diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index c3e8d472..f84b0a9d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -52,6 +52,7 @@ import com.velocitypowered.proxy.connection.util.ServerListPingHandler; import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.crypto.EncryptionUtils; import com.velocitypowered.proxy.event.VelocityEventManager; +import com.velocitypowered.proxy.netease.NeteaseDataManager; import com.velocitypowered.proxy.network.ConnectionManager; import com.velocitypowered.proxy.plugin.VelocityPluginManager; import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer; @@ -286,6 +287,14 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { new GlistCommand(this).register(); new SendCommand(this).register(); + try { + NeteaseDataManager.init(); + } catch (Exception e) { + logger.error("Failed to initialize NeteaseDataManager, please check your config.", e); + LogManager.shutdown(); + System.exit(1); + } + this.doStartupConfigLoad(); for (ServerInfo cliServer : options.getServers()) { @@ -817,7 +826,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { public VelocityChannelRegistrar getChannelRegistrar() { return channelRegistrar; } - + @Override public boolean isShuttingDown() { return shutdownInProgress.get(); 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 00187d34..a280051d 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 @@ -17,7 +17,6 @@ package com.velocitypowered.proxy.connection.client; -import static com.google.common.net.UrlEscapers.urlFormParameterEscaper; import static com.velocitypowered.proxy.VelocityServer.GENERAL_GSON; import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; import static com.velocitypowered.proxy.crypto.EncryptionUtils.decryptRsa; @@ -25,6 +24,7 @@ import static com.velocitypowered.proxy.crypto.EncryptionUtils.generateServerId; import com.google.common.base.Preconditions; import com.google.common.primitives.Longs; +import com.google.gson.JsonObject; import com.velocitypowered.api.event.connection.PreLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult; import com.velocitypowered.api.network.ProtocolVersion; @@ -34,6 +34,8 @@ import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl; +import com.velocitypowered.proxy.netease.AuthResponse; +import com.velocitypowered.proxy.netease.NeteaseDataManager; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; @@ -50,6 +52,7 @@ import java.net.http.HttpResponse; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.MessageDigest; +import java.util.ArrayList; import java.util.Arrays; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; @@ -198,22 +201,21 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { } } - byte[] decryptedSharedSecret = decryptRsa(serverKeyPair, packet.getSharedSecret()); - String serverId = generateServerId(decryptedSharedSecret, serverKeyPair.getPublic()); + final byte[] decryptedSharedSecret = decryptRsa(serverKeyPair, packet.getSharedSecret()); + final String serverId = generateServerId(decryptedSharedSecret, serverKeyPair.getPublic()); + final String playerIp = ((InetSocketAddress) mcConnection.getRemoteAddress()).getHostString(); - String playerIp = ((InetSocketAddress) mcConnection.getRemoteAddress()).getHostString(); - String url = String.format(MOJANG_HASJOINED_URL, - urlFormParameterEscaper().escape(login.getUsername()), serverId); + JsonObject data = new JsonObject(); + data.addProperty("username", login.getUsername()); + data.addProperty("serverId", serverId); + data.addProperty("gameID", NeteaseDataManager.getConfig().getGameId()); - if (server.getConfiguration().shouldPreventClientProxyConnections()) { - url += "&ip=" + urlFormParameterEscaper().escape(playerIp); - } - - final HttpRequest httpRequest = HttpRequest.newBuilder() - .setHeader("User-Agent", - server.getVersion().getName() + "/" + server.getVersion().getVersion()) - .uri(URI.create(url)) + HttpRequest httpRequest = HttpRequest.newBuilder() + .headers("Content-Type", "application/json") + .uri(URI.create(NeteaseDataManager.getConfig().getAuthUrl())) + .POST(HttpRequest.BodyPublishers.ofString(data.toString())) .build(); + final HttpClient httpClient = server.createHttpClient(); httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()) .whenCompleteAsync((response, throwable) -> { @@ -240,31 +242,53 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { return; } - if (response.statusCode() == 200) { - final GameProfile profile = GENERAL_GSON.fromJson(response.body(), - GameProfile.class); - // Not so fast, now we verify the public key for 1.19.1+ - if (inbound.getIdentifiedKey() != null - && inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2 - && inbound.getIdentifiedKey() instanceof final IdentifiedKeyImpl key) { - if (!key.internalAddHolder(profile.getId())) { - inbound.disconnect( - Component.translatable("multiplayer.disconnect.invalid_public_key")); + try { + if (response.statusCode() == 200) { + final AuthResponse authResponse = GENERAL_GSON.fromJson(response.body(), AuthResponse.class); + if (authResponse.getCode() != 0) { + inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); + logger.error("Error authenticating {} with netease", login.getUsername()); + } else { + AuthResponse.ResponseEntity entity = authResponse.getEntity(); + if (entity.getName() == null || entity.getName().isEmpty()) { + entity.setName(login.getUsername()); + } + if (entity.getProperties() == null) { + entity.setProperties(new ArrayList<>()); + } + if (entity.getId() == null) { + inbound.disconnect( + Component.translatable("velocity.error.online-mode-only", NamedTextColor.RED) + ); + } else { + GameProfile profile = new GameProfile(entity.getId(), entity.getName(), entity.getProperties()); + // Not so fast, now we verify the public key for 1.19.1+ + if (inbound.getIdentifiedKey() != null + && inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2 + && inbound.getIdentifiedKey() instanceof final IdentifiedKeyImpl key) { + if (!key.internalAddHolder(profile.getId())) { + inbound.disconnect( + Component.translatable("multiplayer.disconnect.invalid_public_key")); + } + } + // All went well, initialize the session. + mcConnection.setActiveSessionHandler(StateRegistry.LOGIN, + new AuthSessionHandler(server, inbound, profile, true)); + } } + } else if (response.statusCode() == 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 {} ({})", + response.statusCode(), login.getUsername(), playerIp); + inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); } - // All went well, initialize the session. - mcConnection.setActiveSessionHandler(StateRegistry.LOGIN, - new AuthSessionHandler(server, inbound, profile, true)); - } else if (response.statusCode() == 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 {} ({})", - response.statusCode(), login.getUsername(), playerIp); - inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); + } catch (Exception e) { + logger.error("Got an unexpected error", e); } }, mcConnection.eventLoop()) .thenRun(() -> { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/netease/AuthResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/netease/AuthResponse.java new file mode 100644 index 00000000..0701bf83 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/netease/AuthResponse.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2025 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.netease; + +import com.velocitypowered.api.util.GameProfile; +import java.util.List; + +/** + * netease auth response. + */ +public class AuthResponse { + private Integer code = 0; + private String message; + private String details; + private ResponseEntity entity; + + /** + * default constructor. + * + * @param code - + * @param message - + * @param details - + * @param entity the game profile + */ + public AuthResponse(Integer code, String message, String details, ResponseEntity entity) { + this.code = code; + this.message = message; + this.details = details; + this.entity = entity; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getDetails() { + return details; + } + + public void setDetails(String details) { + this.details = details; + } + + public ResponseEntity getEntity() { + return entity; + } + + public void setEntity(ResponseEntity entity) { + this.entity = entity; + } + + /** + * netease auth response entity. + */ + public static class ResponseEntity { + private String id; + private String name; + private List properties; + + /** + * default constructor. + * + * @param id - + * @param name - + * @param properties - + */ + public ResponseEntity(String id, String name, List properties) { + this.id = id; + this.name = name; + this.properties = properties; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } + + @Override + public String toString() { + return "ResponseEntity{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", properties=" + properties + + '}'; + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/netease/NeteaseDataManager.java b/proxy/src/main/java/com/velocitypowered/proxy/netease/NeteaseDataManager.java new file mode 100644 index 00000000..3618ab27 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/netease/NeteaseDataManager.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2025 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.netease; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import org.jetbrains.annotations.NotNull; + +/** + * netease data manager. + */ +public class NeteaseDataManager { + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + private static Config config; + + /** + * init netease data manager. + * + * @throws IOException - + */ + public static void init() throws IOException { + File file = new File("netease.json"); + if (file.exists()) { + String s = Files.readString(file.toPath(), StandardCharsets.UTF_8); + config = GSON.fromJson(s, Config.class); + } else { + config = new Config(); + Files.writeString(file.toPath(), GSON.toJson(config), StandardCharsets.UTF_8); + } + } + + public static Config getConfig() { + return config; + } + + /** + * netease config. + */ + public static class Config { + @NotNull + private String authUrl = "http://192.168.0.100:9999/check"; + @NotNull + private String gameId = "12345678901234567"; + + public @NotNull String getAuthUrl() { + return authUrl; + } + + public void setAuthUrl(@NotNull String authUrl) { + this.authUrl = authUrl; + } + + public @NotNull String getGameId() { + return gameId; + } + + public void setGameId(@NotNull String gameId) { + this.gameId = gameId; + } + } +}