Compare commits
2 Commits
56c7d0d753
...
netease/de
Author | SHA1 | Date | |
---|---|---|---|
ff50d6eabf | |||
0f52dbaaf8 |
@@ -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<Property> 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.
|
||||
*
|
||||
|
@@ -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();
|
||||
|
@@ -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(() -> {
|
||||
|
@@ -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
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 {
|
||||
/**
|
||||
* 认证链接
|
||||
*
|
||||
* <p>正式环境:http://x19authserver.nie.netease.com/check
|
||||
*
|
||||
* <p>测试环境:http://x19authexpr.nie.netease.com/check
|
||||
*
|
||||
* <p>1.20版本请使用以下接口:
|
||||
*
|
||||
* <p>正式环境:https://x19apigatewayobt.nie.netease.com/pcauth/check
|
||||
*
|
||||
* <p>测试环境:https://x19apigatewayexpr.nie.netease.com/pcauth/check
|
||||
*
|
||||
* <p>另有外网测试认证接口:
|
||||
*
|
||||
* <p>http://x19authtest.nie.netease.com/check
|
||||
*
|
||||
* <p>对应接入test版bc认证,通常情况下不使用,需要启用时会另行沟通
|
||||
*/
|
||||
@SuppressWarnings("JavadocLinkAsPlainText")
|
||||
@NotNull
|
||||
private String authUrl = "http://192.168.0.100:9999/check";
|
||||
/**
|
||||
* 网络服游戏 id,在开发者平台中可以查看.
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user