Compare commits

..

2 Commits

Author SHA1 Message Date
ff50d6eabf docs: 完善配置文档 2025-07-30 16:51:02 +08:00
0f52dbaaf8 feat: 魔改网易登录接口
Some checks failed
Java CI with Gradle / build (push) Failing after 2m28s
2025-07-24 13:32:18 +08:00
9 changed files with 259 additions and 167 deletions

View File

@@ -5,7 +5,7 @@ plugins {
} }
java { java {
// withJavadocJar() withJavadocJar()
withSourcesJar() withSourcesJar()
sourceSets["main"].java { sourceSets["main"].java {

View File

@@ -203,8 +203,4 @@ public interface ProxyConfig {
default boolean isKickOnTabCompleteRateLimit() { default boolean isKickOnTabCompleteRateLimit() {
return getKickAfterRateLimitedTabCompletes() > 0; return getKickAfterRateLimitedTabCompletes() > 0;
} }
String getNeteaseAuthUrl();
String getNeteaseGameId();
} }

View File

@@ -225,116 +225,4 @@ public final class GameProfile {
+ '}'; + '}';
} }
} }
/**
* netease auth response.
*/
public static class Response {
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 Response(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<Property> properties;
/**
* default constructor.
*
* @param id -
* @param name -
* @param properties -
*/
public ResponseEntity(String id, String name, List<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<Property> getProperties() {
return properties;
}
public void setProperties(List<Property> properties) {
this.properties = properties;
}
@Override
public String toString() {
return "ResponseEntity{"
+ "id='" + id + '\''
+ ", name='" + name + '\''
+ ", properties=" + properties
+ '}';
}
}
} }

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()) {
@@ -817,7 +826,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
public VelocityChannelRegistrar getChannelRegistrar() { public VelocityChannelRegistrar getChannelRegistrar() {
return channelRegistrar; return channelRegistrar;
} }
@Override @Override
public boolean isShuttingDown() { public boolean isShuttingDown() {
return shutdownInProgress.get(); return shutdownInProgress.get();

View File

@@ -94,11 +94,6 @@ public class VelocityConfiguration implements ProxyConfig {
@Expose @Expose
private boolean forceKeyAuthentication = true; // Added in 1.19 private boolean forceKeyAuthentication = true; // Added in 1.19
@Expose
private String authUrl = "http://192.168.46.50:9999/check";
@Expose
private String gameId = "77140593557373952";
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced, private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
Query query, Metrics metrics) { Query query, Metrics metrics) {
this.servers = servers; this.servers = servers;
@@ -114,7 +109,7 @@ public class VelocityConfiguration implements ProxyConfig {
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough, boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
boolean samplePlayersInPing, boolean enablePlayerAddressLogging, Servers servers, boolean samplePlayersInPing, boolean enablePlayerAddressLogging, Servers servers,
ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics, ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics,
boolean forceKeyAuthentication, String authUrl, String gameId) { boolean forceKeyAuthentication) {
this.bind = bind; this.bind = bind;
this.motd = motd; this.motd = motd;
this.showMaxPlayers = showMaxPlayers; this.showMaxPlayers = showMaxPlayers;
@@ -133,8 +128,6 @@ public class VelocityConfiguration implements ProxyConfig {
this.query = query; this.query = query;
this.metrics = metrics; this.metrics = metrics;
this.forceKeyAuthentication = forceKeyAuthentication; this.forceKeyAuthentication = forceKeyAuthentication;
this.authUrl = authUrl;
this.gameId = gameId;
} }
/** /**
@@ -456,16 +449,6 @@ public class VelocityConfiguration implements ProxyConfig {
return advanced.isEnableReusePort(); return advanced.isEnableReusePort();
} }
@Override
public String getNeteaseAuthUrl() {
return authUrl;
}
@Override
public String getNeteaseGameId() {
return gameId;
}
@Override @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
@@ -483,8 +466,6 @@ public class VelocityConfiguration implements ProxyConfig {
.add("favicon", favicon) .add("favicon", favicon)
.add("enablePlayerAddressLogging", enablePlayerAddressLogging) .add("enablePlayerAddressLogging", enablePlayerAddressLogging)
.add("forceKeyAuthentication", forceKeyAuthentication) .add("forceKeyAuthentication", forceKeyAuthentication)
.add("authUrl", authUrl)
.add("gameId", gameId)
.toString(); .toString();
} }
@@ -584,8 +565,6 @@ public class VelocityConfiguration implements ProxyConfig {
|| forwardingMode == PlayerInfoForwarding.BUNGEEGUARD)) { || forwardingMode == PlayerInfoForwarding.BUNGEEGUARD)) {
throw new RuntimeException("The forwarding-secret file must not be empty."); throw new RuntimeException("The forwarding-secret file must not be empty.");
} }
final String authUrl = config.getOrElse("auth-url", "http://192.168.46.50:9999/check");
final String gameId = config.getOrElse("game-id", "77140593557373952");
return new VelocityConfiguration( return new VelocityConfiguration(
bind, bind,
@@ -605,9 +584,7 @@ public class VelocityConfiguration implements ProxyConfig {
new Advanced(advancedConfig), new Advanced(advancedConfig),
new Query(queryConfig), new Query(queryConfig),
new Metrics(metricsConfig), new Metrics(metricsConfig),
forceKeyAuthentication, forceKeyAuthentication
authUrl,
gameId
); );
} }
} }

View File

@@ -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;
@@ -206,14 +208,12 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
JsonObject data = new JsonObject(); JsonObject data = new JsonObject();
data.addProperty("username", login.getUsername()); data.addProperty("username", login.getUsername());
data.addProperty("serverId", serverId); data.addProperty("serverId", serverId);
data.addProperty("gameID", server.getConfiguration().getNeteaseGameId()); data.addProperty("gameID", NeteaseDataManager.getConfig().getGameId());
HttpRequest httpRequest = HttpRequest.newBuilder() HttpRequest httpRequest = HttpRequest.newBuilder()
.headers( .headers("Content-Type", "application/json")
"User-Agent", server.getVersion().getName() + "/" + server.getVersion().getVersion(), .uri(URI.create(NeteaseDataManager.getConfig().getAuthUrl()))
"Content-Type", "application/json" .POST(HttpRequest.BodyPublishers.ofString(data.toString()))
).POST(HttpRequest.BodyPublishers.ofString(data.toString()))
.uri(URI.create(server.getConfiguration().getNeteaseAuthUrl()))
.build(); .build();
final HttpClient httpClient = server.createHttpClient(); final HttpClient httpClient = server.createHttpClient();
@@ -244,13 +244,12 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
try { try {
if (response.statusCode() == 200) { if (response.statusCode() == 200) {
final GameProfile.Response authResponse = GENERAL_GSON.fromJson(response.body(), final AuthResponse authResponse = GENERAL_GSON.fromJson(response.body(), AuthResponse.class);
GameProfile.Response.class);
if (authResponse.getCode() != 0) { if (authResponse.getCode() != 0) {
inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
logger.error("Error authenticating {} with netease", login.getUsername()); logger.error("Error authenticating {} with netease", login.getUsername());
} else { } else {
GameProfile.ResponseEntity entity = authResponse.getEntity(); AuthResponse.ResponseEntity entity = authResponse.getEntity();
if (entity.getName() == null || entity.getName().isEmpty()) { if (entity.getName() == null || entity.getName().isEmpty()) {
entity.setName(login.getUsername()); entity.setName(login.getUsername());
} }
@@ -259,7 +258,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
} }
if (entity.getId() == null) { if (entity.getId() == null) {
inbound.disconnect( inbound.disconnect(
Component.translatable("velocity.error.online-mode-only", NamedTextColor.RED)); Component.translatable("velocity.error.online-mode-only", NamedTextColor.RED)
);
} else { } else {
GameProfile profile = new GameProfile(entity.getId(), entity.getName(), entity.getProperties()); 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+

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,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;
}
}
}

View File

@@ -74,20 +74,6 @@ sample-players-in-ping = false
# If not enabled (default is true) player IP addresses will be replaced by <ip address withheld> in logs # If not enabled (default is true) player IP addresses will be replaced by <ip address withheld> in logs
enable-player-address-logging = true enable-player-address-logging = true
# 认证链接
# 正式环境http://x19authserver.nie.netease.com/check
# 测试环境http://x19authexpr.nie.netease.com/check
# 1.20版本请使用以下接口:
# 正式环境https://x19apigatewayobt.nie.netease.com/pcauth/check
# 测试环境https://x19apigatewayexpr.nie.netease.com/pcauth/check
# 另有外网测试认证接口对应接入test版bc认证通常情况下不使用需要启用时会另行沟通。
# http://x19authtest.nie.netease.com/check
auth-url = "http://192.168.46.50:9999/check"
# 网络服游戏 id
# 在开发者平台中可以查看
game-id = "77140593557373952"
[servers] [servers]
# Configure your servers here. Each key represents the server's name, and the value # Configure your servers here. Each key represents the server's name, and the value
# represents the IP address of the server to connect to. # represents the IP address of the server to connect to.