feat: 魔改网易登录接口
Some checks failed
Java CI with Gradle / build (push) Failing after 2m28s

This commit is contained in:
2025-01-28 19:20:32 +08:00
parent e99407132f
commit 0f52dbaaf8
5 changed files with 289 additions and 39 deletions

View File

@@ -19,7 +19,7 @@ public final class GameProfile {
private final UUID id; private final UUID id;
private final String undashedId; private final String undashedId;
private final String name; private String name;
private final List<Property> properties; private final List<Property> properties;
/** /**
@@ -80,6 +80,10 @@ public final class GameProfile {
return name; return name;
} }
public void setName(String name) {
this.name = name;
}
/** /**
* Returns an immutable list of profile properties associated with this profile. * Returns an immutable list of profile properties associated with this profile.
* *

View File

@@ -52,6 +52,7 @@ import com.velocitypowered.proxy.connection.util.ServerListPingHandler;
import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.console.VelocityConsole;
import com.velocitypowered.proxy.crypto.EncryptionUtils; import com.velocitypowered.proxy.crypto.EncryptionUtils;
import com.velocitypowered.proxy.event.VelocityEventManager; import com.velocitypowered.proxy.event.VelocityEventManager;
import com.velocitypowered.proxy.netease.NeteaseDataManager;
import com.velocitypowered.proxy.network.ConnectionManager; import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.plugin.VelocityPluginManager; import com.velocitypowered.proxy.plugin.VelocityPluginManager;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer; import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer;
@@ -286,6 +287,14 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
new GlistCommand(this).register(); new GlistCommand(this).register();
new SendCommand(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(); this.doStartupConfigLoad();
for (ServerInfo cliServer : options.getServers()) { for (ServerInfo cliServer : options.getServers()) {

View File

@@ -17,7 +17,6 @@
package com.velocitypowered.proxy.connection.client; 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.VelocityServer.GENERAL_GSON;
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
import static com.velocitypowered.proxy.crypto.EncryptionUtils.decryptRsa; 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.base.Preconditions;
import com.google.common.primitives.Longs; 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;
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult; import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
import com.velocitypowered.api.network.ProtocolVersion; 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.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl; 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.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
@@ -50,6 +52,7 @@ import java.net.http.HttpResponse;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@@ -198,22 +201,21 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
} }
} }
byte[] decryptedSharedSecret = decryptRsa(serverKeyPair, packet.getSharedSecret()); final byte[] decryptedSharedSecret = decryptRsa(serverKeyPair, packet.getSharedSecret());
String serverId = generateServerId(decryptedSharedSecret, serverKeyPair.getPublic()); final String serverId = generateServerId(decryptedSharedSecret, serverKeyPair.getPublic());
final String playerIp = ((InetSocketAddress) mcConnection.getRemoteAddress()).getHostString();
String playerIp = ((InetSocketAddress) mcConnection.getRemoteAddress()).getHostString(); JsonObject data = new JsonObject();
String url = String.format(MOJANG_HASJOINED_URL, data.addProperty("username", login.getUsername());
urlFormParameterEscaper().escape(login.getUsername()), serverId); data.addProperty("serverId", serverId);
data.addProperty("gameID", NeteaseDataManager.getConfig().getGameId());
if (server.getConfiguration().shouldPreventClientProxyConnections()) { HttpRequest httpRequest = HttpRequest.newBuilder()
url += "&ip=" + urlFormParameterEscaper().escape(playerIp); .headers("Content-Type", "application/json")
} .uri(URI.create(NeteaseDataManager.getConfig().getAuthUrl()))
.POST(HttpRequest.BodyPublishers.ofString(data.toString()))
final HttpRequest httpRequest = HttpRequest.newBuilder()
.setHeader("User-Agent",
server.getVersion().getName() + "/" + server.getVersion().getVersion())
.uri(URI.create(url))
.build(); .build();
final HttpClient httpClient = server.createHttpClient(); final HttpClient httpClient = server.createHttpClient();
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()) httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.whenCompleteAsync((response, throwable) -> { .whenCompleteAsync((response, throwable) -> {
@@ -240,9 +242,26 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
return; return;
} }
try {
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
final GameProfile profile = GENERAL_GSON.fromJson(response.body(), final AuthResponse authResponse = GENERAL_GSON.fromJson(response.body(), AuthResponse.class);
GameProfile.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+ // Not so fast, now we verify the public key for 1.19.1+
if (inbound.getIdentifiedKey() != null if (inbound.getIdentifiedKey() != null
&& inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2 && inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2
@@ -255,6 +274,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
// All went well, initialize the session. // All went well, initialize the session.
mcConnection.setActiveSessionHandler(StateRegistry.LOGIN, mcConnection.setActiveSessionHandler(StateRegistry.LOGIN,
new AuthSessionHandler(server, inbound, profile, true)); new AuthSessionHandler(server, inbound, profile, true));
}
}
} else if (response.statusCode() == 204) { } else if (response.statusCode() == 204) {
// Apparently an offline-mode user logged onto this online-mode proxy. // Apparently an offline-mode user logged onto this online-mode proxy.
inbound.disconnect( inbound.disconnect(
@@ -266,6 +287,9 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
response.statusCode(), login.getUsername(), playerIp); response.statusCode(), login.getUsername(), playerIp);
inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
} }
} catch (Exception e) {
logger.error("Got an unexpected error", e);
}
}, mcConnection.eventLoop()) }, mcConnection.eventLoop())
.thenRun(() -> { .thenRun(() -> {
if (httpClient instanceof final AutoCloseable closeable) { if (httpClient instanceof final AutoCloseable closeable) {

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<GameProfile.Property> properties;
/**
* default constructor.
*
* @param id -
* @param name -
* @param properties -
*/
public ResponseEntity(String id, String name, List<GameProfile.Property> 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<GameProfile.Property> getProperties() {
return properties;
}
public void setProperties(List<GameProfile.Property> properties) {
this.properties = properties;
}
@Override
public String toString() {
return "ResponseEntity{"
+ "id='" + id + '\''
+ ", name='" + name + '\''
+ ", properties=" + properties
+ '}';
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}
}