mirror of
https://github.com/layue13/MoonBridge.git
synced 2026-10-25 18:46:18 +00:00
perf: Spigot/CraftBukkit 1.7.10 上读取 GameProfile 也不再用反射
- SpigotProfileAccess:直接调用 org.bukkit.craftbukkit.v1_7_R4.entity.CraftPlayer#getProfile 与重定位到 net.minecraft.util.com.mojang.authlib 的 authlib。签名用 javap 从 spigot-1.7.10-R0.1-SNAPSHOT(build 1649)和 craftbukkit-1.7.10-R0.1 读取,两者一致; 五个调用的描述符逐一与 Spigot JAR 核对 - 选择顺序:Uranium → Spigot 1.7.10 → 缓存反射回退 - src/serverStubs 加入对应的编译期副本;verifyBackendJar 额外拒绝 net/minecraft/ - 测试:SpigotProfileAccessTest;文档更新 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
36dd9ebb33
commit
547ba98f8f
11 files changed
+166
-9
No files matched your search
@@ -63,7 +63,9 @@ val verifyBackendJar = tasks.register("verifyBackendJar") {
|
||||
check(names.count { name -> name == it } == 1) { "Backend jar must contain exactly one $it" }
|
||||
}
|
||||
check(names.none { it.startsWith("org/bukkit/") }) { "Bukkit API must remain server-provided" }
|
||||
check(names.none { it.startsWith("com/mojang/") }) { "authlib must remain server-provided" }
|
||||
check(names.none { it.startsWith("com/mojang/") || it.startsWith("net/minecraft/") }) {
|
||||
"authlib and Minecraft classes must remain server-provided"
|
||||
}
|
||||
entries.filter { it.name.endsWith(".class") }.forEach { entry ->
|
||||
DataInputStream(archive.getInputStream(entry)).use { input ->
|
||||
input.readInt()
|
||||
|
||||
@@ -5,7 +5,8 @@ import java.util.logging.Logger;
|
||||
/**
|
||||
* How the backend reaches a player's GameProfile properties on the running server. Bukkit has no API for them, so
|
||||
* each server family needs its own access. A compiled adapter is chosen once at startup; servers without one use the
|
||||
* cached reflective fallback.
|
||||
* cached reflective fallback. The two server families never share class names (CraftBukkit's package is versioned
|
||||
* only on Spigot, and only Spigot relocates authlib), so at most one compiled adapter can load.
|
||||
*/
|
||||
interface ProfileAccess {
|
||||
/** True when this access knows the player's class; other players go to the reflective fallback. */
|
||||
@@ -25,13 +26,17 @@ interface ProfileAccess {
|
||||
/** A short name for the startup log. */
|
||||
String description();
|
||||
|
||||
/** The compiled Uranium adapter when its classes are present, otherwise the reflective fallback. */
|
||||
/** The first compiled adapter whose classes are present (Uranium, then Spigot 1.7.10), else the fallback. */
|
||||
static ProfileAccess select() {
|
||||
ProfileAccess selected;
|
||||
try {
|
||||
selected = UraniumProfileAccess.create();
|
||||
} catch (LinkageError notUranium) {
|
||||
selected = ReflectiveProfileAccess.INSTANCE;
|
||||
try {
|
||||
selected = SpigotProfileAccess.create();
|
||||
} catch (LinkageError notSpigot) {
|
||||
selected = ReflectiveProfileAccess.INSTANCE;
|
||||
}
|
||||
}
|
||||
Logger.getLogger(ProfileAccess.class.getName()).info("GameProfile access: " + selected.description());
|
||||
return selected;
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Fallback for servers without a compiled adapter (Spigot/CraftBukkit 1.7.10 and others): finds the GameProfile
|
||||
* Fallback for servers without a compiled adapter (neither Uranium nor Spigot/CraftBukkit 1.7.10): finds the GameProfile
|
||||
* methods by name. Lookups are resolved once per receiver class and cached, including misses; a missing method means
|
||||
* the feature is unavailable on this server, never a guess.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package dev.moonbridge.bukkit;
|
||||
|
||||
import net.minecraft.util.com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.util.com.mojang.authlib.properties.Property;
|
||||
import net.minecraft.util.com.mojang.authlib.properties.PropertyMap;
|
||||
import org.bukkit.craftbukkit.v1_7_R4.entity.CraftPlayer;
|
||||
|
||||
/**
|
||||
* Direct calls into Spigot and CraftBukkit 1.7.10 (v1_7_R4), which relocate authlib and Guava under
|
||||
* {@code net.minecraft.util}. Compiled against the stubs in src/serverStubs, whose signatures were read with javap from
|
||||
* spigot-1.7.10-R0.1-SNAPSHOT (build 1649) and craftbukkit-1.7.10-R0.1; both builds declare them identically.
|
||||
*/
|
||||
final class SpigotProfileAccess implements ProfileAccess {
|
||||
private SpigotProfileAccess() { }
|
||||
|
||||
/** Throws {@link LinkageError} when the server lacks these classes, so selection happens at startup. */
|
||||
static SpigotProfileAccess create() {
|
||||
// Class literals load the classes now rather than on the first join.
|
||||
Class<?>[] required = {CraftPlayer.class, GameProfile.class, PropertyMap.class, Property.class};
|
||||
return required.length > 0 ? new SpigotProfileAccess() : null;
|
||||
}
|
||||
|
||||
@Override public boolean handles(Object player) { return player instanceof CraftPlayer; }
|
||||
|
||||
@Override public Object properties(Object player) {
|
||||
if (!(player instanceof CraftPlayer)) return null;
|
||||
GameProfile profile = ((CraftPlayer) player).getProfile();
|
||||
return profile == null ? null : profile.getProperties();
|
||||
}
|
||||
|
||||
@Override public Iterable<?> get(Object properties, String name) {
|
||||
return ((PropertyMap) properties).get(name);
|
||||
}
|
||||
|
||||
@Override public boolean remove(Object properties, String name, Object property) {
|
||||
return ((PropertyMap) properties).remove(name, property);
|
||||
}
|
||||
|
||||
@Override public Object value(Object property) {
|
||||
return ((Property) property).getValue();
|
||||
}
|
||||
|
||||
@Override public String description() { return "compiled Spigot/CraftBukkit 1.7.10 adapter"; }
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
Compile-only copies of the server classes `UraniumProfileAccess` calls. Their names and signatures match Uranium
|
||||
(`TDLM/Uranium`, `org.bukkit.craftbukkit.entity.CraftPlayer#getProfile`) and authlib 1.5.16 exactly; the bodies only
|
||||
serve tests. This source set is never packaged: the running server provides the real classes, and
|
||||
Compile-only copies of the server classes the compiled profile adapters call; the bodies only serve tests.
|
||||
|
||||
- `UraniumProfileAccess`: Uranium (`TDLM/Uranium`, `org.bukkit.craftbukkit.entity.CraftPlayer#getProfile`) and
|
||||
authlib 1.5.16 (`com.mojang.authlib`).
|
||||
- `SpigotProfileAccess`: Spigot and CraftBukkit 1.7.10 (`org.bukkit.craftbukkit.v1_7_R4.entity.CraftPlayer`, authlib
|
||||
relocated to `net.minecraft.util.com.mojang.authlib`), read with javap from spigot-1.7.10-R0.1-SNAPSHOT build 1649
|
||||
and craftbukkit-1.7.10-R0.1. This source set is never packaged: the running server provides the real classes, and
|
||||
`verifyBackendJar` fails if any of them reaches the backend jar.
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.minecraft.util.com.mojang.authlib;
|
||||
|
||||
import net.minecraft.util.com.mojang.authlib.properties.PropertyMap;
|
||||
|
||||
/** Stub of the GameProfile Spigot/CraftBukkit 1.7.10 relocates under net.minecraft.util. */
|
||||
public class GameProfile {
|
||||
private final PropertyMap properties = new PropertyMap();
|
||||
|
||||
public PropertyMap getProperties() { return properties; }
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package net.minecraft.util.com.mojang.authlib.properties;
|
||||
|
||||
/** Stub of the relocated Property. */
|
||||
public class Property {
|
||||
private final String value;
|
||||
|
||||
public Property(String name, String value) { this.value = value; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package net.minecraft.util.com.mojang.authlib.properties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Stub of the relocated PropertyMap. The real class extends the relocated Guava ForwardingMultimap, which declares
|
||||
* these two methods; calls compiled against this stub resolve to them at run time.
|
||||
*/
|
||||
public class PropertyMap {
|
||||
private final Map<Object, List<Property>> entries = new HashMap<Object, List<Property>>();
|
||||
|
||||
public Collection<Property> get(Object key) {
|
||||
List<Property> values = entries.get(key);
|
||||
return values == null ? new ArrayList<Property>() : new ArrayList<Property>(values);
|
||||
}
|
||||
|
||||
public boolean remove(Object key, Object value) {
|
||||
List<Property> values = entries.get(key);
|
||||
return values != null && values.remove(value);
|
||||
}
|
||||
|
||||
/** Test helper only; the backend never calls it. */
|
||||
public boolean put(String key, Property value) {
|
||||
List<Property> values = entries.get(key);
|
||||
if (values == null) entries.put(key, values = new ArrayList<Property>());
|
||||
return values.add(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.bukkit.craftbukkit.v1_7_R4.entity;
|
||||
|
||||
import net.minecraft.util.com.mojang.authlib.GameProfile;
|
||||
|
||||
/** Stub of Spigot/CraftBukkit 1.7.10's CraftPlayer: only the method the backend calls. */
|
||||
public class CraftPlayer {
|
||||
private final GameProfile profile;
|
||||
|
||||
public CraftPlayer(GameProfile profile) { this.profile = profile; }
|
||||
|
||||
public GameProfile getProfile() { return profile; }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package dev.moonbridge.bukkit;
|
||||
|
||||
import dev.moonbridge.messaging.session.ForwardedSessionProof;
|
||||
import net.minecraft.util.com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.util.com.mojang.authlib.properties.Property;
|
||||
import org.bukkit.craftbukkit.v1_7_R4.entity.CraftPlayer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/** The compiled Spigot/CraftBukkit 1.7.10 adapter against stubs carrying the javap-verified signatures. */
|
||||
class SpigotProfileAccessTest {
|
||||
@Test void compiledAdapterRemovesTheProofAndKeepsOtherProperties() {
|
||||
GameProfile profile = new GameProfile();
|
||||
Property proof = new Property(ForwardedSessionProof.PROPERTY_NAME, "proof-value");
|
||||
Property textures = new Property("textures", "texture-value");
|
||||
profile.getProperties().put(ForwardedSessionProof.PROPERTY_NAME, proof);
|
||||
profile.getProperties().put("textures", textures);
|
||||
SpigotProfileAccess access = SpigotProfileAccess.create();
|
||||
CraftPlayer player = new CraftPlayer(profile);
|
||||
|
||||
assertTrue(access.handles(player));
|
||||
OwnedBukkitSessionService.ProofProperty stripped = OwnedBukkitSessionService.stripForwardedProof(access, player);
|
||||
|
||||
assertTrue(stripped.removed);
|
||||
assertEquals("proof-value", stripped.proof);
|
||||
assertTrue(profile.getProperties().get(ForwardedSessionProof.PROPERTY_NAME).isEmpty());
|
||||
assertSame(textures, profile.getProperties().get("textures").iterator().next());
|
||||
}
|
||||
|
||||
@Test void eachAdapterOnlyHandlesItsOwnServersPlayers() {
|
||||
Object uraniumPlayer = new org.bukkit.craftbukkit.entity.CraftPlayer(new com.mojang.authlib.GameProfile());
|
||||
Object spigotPlayer = new CraftPlayer(new GameProfile());
|
||||
assertFalse(SpigotProfileAccess.create().handles(uraniumPlayer));
|
||||
assertFalse(UraniumProfileAccess.create().handles(spigotPlayer));
|
||||
assertTrue(SpigotProfileAccess.create().handles(spigotPlayer));
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ Proxy 为每个插件管理独立的 scope,在停用时自动撤销。项目
|
||||
|
||||
在 `OFFLINE` 代理模式中,只有显式配置了该 backend 的 session-binding secret 时,代理才会在初始连接或转服时转发由代理派生的 offline UUID 和会话证明;这证明代理连接身份,不表示 Mojang 已认证。没有配置 secret 的 offline backend 仍接收原始握手。静态 backend 或尚未获得正数 registration epoch 的目标不会收到会话证明。
|
||||
|
||||
后端宿主在玩家加入时从 GameProfile 属性中取出并移除会话证明。Bukkit API 不提供 GameProfile,所以按服务器选择访问方式,启动日志会写明 `GameProfile access: …`:Uranium(`org.bukkit.craftbukkit.entity.CraftPlayer`、未重定位的 authlib)使用编译进 JAR 的直接调用,没有反射;其他服务器(例如 Spigot/CraftBukkit 1.7.10)使用按类缓存的反射回退。编译调用与服务器实际签名不符时,宿主记一条警告后改用反射回退。两条路径都找不到 GameProfile 时,该玩家没有会话绑定,不会猜测。
|
||||
后端宿主在玩家加入时从 GameProfile 属性中取出并移除会话证明。Bukkit API 不提供 GameProfile,所以按服务器选择访问方式,启动日志会写明 `GameProfile access: …`:Uranium(`org.bukkit.craftbukkit.entity.CraftPlayer`、未重定位的 authlib)和 Spigot/CraftBukkit 1.7.10(`v1_7_R4`、authlib 重定位到 `net.minecraft.util`)都使用编译进 JAR 的直接调用,没有反射;其他服务器使用按类缓存的反射回退。编译调用与服务器实际签名不符时,宿主记一条警告后改用反射回退。两条路径都找不到 GameProfile 时,该玩家没有会话绑定,不会猜测。
|
||||
|
||||
实例的游戏地址必须是代理实际可达的 `tcp://host:port`。Docker 中使用容器服务名,容器间不要使用 `127.0.0.1`。仅将玩家端口公开,控制端口放在私有网络。不要把真实凭据写入镜像或提交到仓库。
|
||||
|
||||
|
||||
Reference in new issue
Block a user