Skip to content

Commit 7f8b30d

Browse files
authored
Merge pull request #625 from YsGqHY/dev/6.2.3-dev
feat(nms): 添加对混合服务端的支持并优化发包逻辑
2 parents 1c0dbc5 + a1abd59 commit 7f8b30d

File tree

2 files changed

+169
-13
lines changed

2 files changed

+169
-13
lines changed

module/bukkit-nms/src/main/kotlin/taboolib/module/nms/MinecraftVersion.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ object MinecraftVersion {
6262
val isUniversalCraftBukkit: Boolean
6363
get() = minecraftVersion == "UNKNOWN"
6464

65+
/**
66+
* 是否为CatServer
67+
* 这些服务端使用自己的重混淆系统,可能与 Taboolib 的 NMS 重映射不兼容
68+
*/
69+
val isCatServer by unsafeLazy {
70+
try {
71+
// 检测 CatServer
72+
Class.forName("catserver.server.CatServer")
73+
return@unsafeLazy true
74+
} catch (_: ClassNotFoundException) {
75+
}
76+
false
77+
}
78+
6579
/**
6680
* 当前所有受支持的版本
6781
*/

module/bukkit-nms/src/main/kotlin/taboolib/module/nms/PacketSender.kt

Lines changed: 155 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import java.util.concurrent.ConcurrentHashMap
2727
object PacketSender {
2828

2929
private val playerConnectionMap = ConcurrentHashMap<String, Any>()
30-
private var sendPacketMethod: ClassMethod? = null
30+
private var sendPacketMethod: Any? = null
31+
private var isFallbackMethod = false
3132

3233
private var newPacketBundlePacket: Constructor<*>? = null
3334
private var useMinecraftMethod = false
@@ -66,19 +67,88 @@ object PacketSender {
6667
// 之前通过 TinyProtocol 的 channel.pipeline().writeAndFlush() 暴力发包会有概率出问题
6768
val connection = getConnection(player)
6869
if (sendPacketMethod == null) {
69-
val reflexClass = ReflexClass.of(connection.javaClass)
70-
// 1.18 更名为 send 方法
71-
sendPacketMethod = if (MinecraftVersion.isHigherOrEqual(MinecraftVersion.V1_18)) {
70+
// 在混合服务端下,直接使用原生反射避免触发类型加载
71+
if (MinecraftVersion.isCatServer) {
72+
sendPacketMethod = getSendPacketMethodHybrid(connection, packet)
73+
isFallbackMethod = true
74+
} else {
7275
try {
73-
reflexClass.getMethod("send", true, true, packet)
74-
} catch (_: NoSuchMethodException) {
75-
reflexClass.getMethod("sendPacket", true, true, packet)
76+
val reflexClass = ReflexClass.of(connection.javaClass)
77+
sendPacketMethod = if (MinecraftVersion.isHigherOrEqual(MinecraftVersion.V1_18)) {
78+
try {
79+
reflexClass.getMethod("send", true, true, packet)
80+
} catch (_: NoSuchMethodException) {
81+
reflexClass.getMethod("sendPacket", true, true, packet)
82+
}
83+
} else {
84+
reflexClass.getMethod("sendPacket", true, true, packet)
85+
}
86+
isFallbackMethod = false
87+
} catch (e: Exception) {
88+
// 如果 ReflexClass 获取方法失败,使用回退方案
89+
sendPacketMethod = getSendPacketMethodHybrid(connection, packet)
90+
isFallbackMethod = true
91+
}
92+
}
93+
}
94+
95+
// 根据方法类型调用
96+
if (isFallbackMethod) {
97+
(sendPacketMethod as java.lang.reflect.Method).invoke(connection, packet)
98+
} else {
99+
(sendPacketMethod as ClassMethod).invoke(connection, packet)
100+
}
101+
}
102+
103+
/**
104+
* 获取发送数据包方法的回退方案
105+
* 用于处理混合服务端(CatServer等)的兼容性问题
106+
*
107+
* 注意:在混合服务端下必须使用原生 Java 反射
108+
* 因为 TabooLib 的 Reflex API 会尝试获取父类和返回类型
109+
* 这会触发 Class.forName() 加载混淆的类名,导致 CatServer 的 remapper 报错
110+
*/
111+
private fun getSendPacketMethodHybrid(connection: Any, packet: Any): java.lang.reflect.Method {
112+
val connectionClass = connection.javaClass
113+
114+
val methodNames = if (MinecraftVersion.isHigherOrEqual(MinecraftVersion.V1_18)) {
115+
listOf("send", "a", "sendPacket")
116+
} else {
117+
listOf("sendPacket", "a", "send")
118+
}
119+
120+
// 首先尝试查找接受 Packet 基类或接口的方法(更通用)
121+
for (methodName in methodNames) {
122+
for (method in connectionClass.declaredMethods) {
123+
if (method.name == methodName && method.parameterCount == 1) {
124+
val paramType = method.parameterTypes[0]
125+
// 检查参数类型是否是 Packet 基类或接口
126+
if (paramType.isAssignableFrom(packet.javaClass)) {
127+
method.isAccessible = true
128+
return method
129+
}
76130
}
77-
} else {
78-
reflexClass.getMethod("sendPacket", true, true, packet)
79131
}
80132
}
81-
sendPacketMethod!!.invoke(connection, packet)
133+
134+
// 如果找不到通用方法,尝试精确匹配具体的数据包类型
135+
for (methodName in methodNames) {
136+
try {
137+
val method = connectionClass.getDeclaredMethod(methodName, packet.javaClass)
138+
method.isAccessible = true
139+
// 缓存找到的方法
140+
return method
141+
} catch (_: NoSuchMethodException) {
142+
// 尝试下一个方法名
143+
}
144+
}
145+
146+
throw NoSuchMethodException(
147+
"Failed to find sendPacket method. " +
148+
"Connection type: ${connectionClass.name}, " +
149+
"Packet type: ${packet.javaClass.name}, " +
150+
"Server: ${if (MinecraftVersion.isCatServer) "Hybrid Server" else "Standard Server"}"
151+
)
82152
}
83153

84154
/**
@@ -88,16 +158,88 @@ object PacketSender {
88158
return if (playerConnectionMap.containsKey(player.name)) {
89159
playerConnectionMap[player.name]!!
90160
} else {
91-
val connection = if (MinecraftVersion.isUniversal) {
92-
player.getProperty<Any>("entity/connection")!!
161+
val connection = if (!MinecraftVersion.isCatServer) {
162+
// 标准方式获取连接
163+
if (MinecraftVersion.isUniversal) {
164+
player.getProperty<Any>("entity/connection")!!
165+
} else {
166+
player.getProperty<Any>("entity/playerConnection")!!
167+
}
93168
} else {
94-
player.getProperty<Any>("entity/playerConnection")!!
169+
// 因为 getProperty 会尝试获取字段类型,触发 Class.forName() 导致 ClassNotFoundException
170+
getConnectionHybrid(player)
95171
}
96172
playerConnectionMap[player.name] = connection
97173
connection
98174
}
99175
}
100176

177+
/**
178+
* 获取玩家连接的回退方案
179+
* 用于处理混合服务端(CatServer等)的兼容性问题
180+
*
181+
* 注意:在混合服务端下必须使用原生 Java 反射
182+
* 因为 TabooLib 的 Reflex API 会尝试获取返回类型和字段类型
183+
* 这会触发 Class.forName() 加载混淆的类名,导致 CatServer 的 remapper 报错
184+
*/
185+
private fun getConnectionHybrid(player: Player): Any {
186+
try {
187+
val playerClass = player.javaClass
188+
189+
val getHandleMethod = playerClass.getDeclaredMethod("getHandle")
190+
getHandleMethod.isAccessible = true
191+
192+
val entityPlayer = getHandleMethod.invoke(player)
193+
val entityPlayerClass = entityPlayer.javaClass
194+
195+
val fieldNames = if (MinecraftVersion.isUniversal) {
196+
listOf("connection", "b", "c", "playerConnection")
197+
} else {
198+
listOf("playerConnection", "b", "c", "connection")
199+
}
200+
201+
for (fieldName in fieldNames) {
202+
try {
203+
val field = entityPlayerClass.getDeclaredField(fieldName)
204+
field.isAccessible = true
205+
val connection = field.get(entityPlayer)
206+
if (connection != null) {
207+
// 缓存找到的字段
208+
return connection
209+
}
210+
} catch (_: NoSuchFieldException) {
211+
continue
212+
} catch (_: IllegalAccessException) {
213+
continue
214+
}
215+
}
216+
217+
for (field in entityPlayerClass.declaredFields) {
218+
val fieldType = field.type.simpleName
219+
if (fieldType.contains("Connection") || fieldType.contains("PlayerConnection")) {
220+
try {
221+
field.isAccessible = true
222+
val connection = field.get(entityPlayer)
223+
if (connection != null) {
224+
return connection
225+
}
226+
} catch (_: Exception) {
227+
continue
228+
}
229+
}
230+
}
231+
} catch (e: Exception) {
232+
e.printStackTrace()
233+
}
234+
235+
// 还找不到就真没招了
236+
throw RuntimeException(
237+
"Failed to get player connection. " +
238+
"Server: ${if (MinecraftVersion.isCatServer) "Hybrid Server" else "Standard Server"}, " +
239+
"Version: ${MinecraftVersion.runningVersion}"
240+
)
241+
}
242+
101243
@SubscribeEvent
102244
private fun onJoin(e: PlayerJoinEvent) {
103245
playerConnectionMap.remove(e.player.name)

0 commit comments

Comments
 (0)