diff --git a/README.md b/README.md
index 48bb6f0ed7..232099ceca 100644
--- a/README.md
+++ b/README.md
@@ -9,11 +9,18 @@ A list of small tweaks I made:
- Implement player UUID rewrite, like what bungeecord does.
Make setup with online velocity + offline Minecraft server work correctly
(`online-mode=true` on velocity + `online-mode=false` on backend mc servers + `player-info-forwarding-mode=none`)
- - TabList packets rewrite
- - Affects `LegacyPlayerListItemPacket`, `UpsertPlayerInfoPacket`, `RemovePlayerInfoPacket` packets
- - Rewrites player UUIDs inside those packets to their UUIDs in the velocity server
- - Entity packets rewrite
- - Rewrites player UUIDs inside player creation packets and spectator teleport packets, to their UUIDs in the velocity server
+ - Packets to rewrite:
+ - TabList packets
+ - Affects `LegacyPlayerListItemPacket`, `UpsertPlayerInfoPacket`, `RemovePlayerInfoPacket` packets
+ - Rewrites player UUIDs inside those packets to their UUIDs in the velocity server
+ - Entity packets
+ - Rewrites player UUIDs inside player creation packets and spectator teleport packets, to their UUIDs in the velocity server
+ - All related configs are under section `uuid-rewrite` in `velocity.toml`
+ - Optional external uuid mapping sqlite database support
+ - Enabled with `databaseEnabled = true`, database path configurable with `databasePath`
+ - Mapping between online / offline uuid will be updated on player connected
+ - The sqlite database file can be shared between multiple velocity instances
+ - UUID rewrite can be disabled by setting `enabled = false`
# Velocity
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a3d0523dcb..7ca1ad841d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -57,6 +57,7 @@ nightconfig = "com.electronwill.night-config:toml:3.6.7"
slf4j = "org.slf4j:slf4j-api:2.0.12"
snakeyaml = "org.yaml:snakeyaml:1.33"
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3"
+sqlite-jdbc = "org.xerial:sqlite-jdbc:3.46.0.1" # [fallen's fork] player uuid rewrite - uuid database
terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0"
[bundles]
diff --git a/proxy/build.gradle.kts b/proxy/build.gradle.kts
index 5e1387b06c..d60773862e 100644
--- a/proxy/build.gradle.kts
+++ b/proxy/build.gradle.kts
@@ -126,6 +126,7 @@ dependencies {
implementation(libs.lmbda)
implementation(libs.asm)
implementation(libs.bundles.flare)
+ implementation(libs.sqlite.jdbc) // [fallen's fork] player uuid rewrite - uuid database
compileOnly(libs.spotbugs.annotations)
compileOnly(libs.auto.service.annotations)
testImplementation(libs.mockito)
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
index 192153a085..6de4cfb19d 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
@@ -53,7 +53,6 @@
import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.plugin.VelocityPluginManager;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
-import com.velocitypowered.proxy.protocol.packet.uuidrewrite.TabListUuidRewriter;
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
import com.velocitypowered.proxy.protocol.util.GameProfileSerializer;
import com.velocitypowered.proxy.scheduler.VelocityScheduler;
@@ -64,6 +63,7 @@
import com.velocitypowered.proxy.util.VelocityChannelRegistrar;
import com.velocitypowered.proxy.util.ratelimit.Ratelimiter;
import com.velocitypowered.proxy.util.ratelimit.Ratelimiters;
+import com.velocitypowered.proxy.uuidrewrite.UuidRewriteHooks;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
@@ -264,6 +264,9 @@ void start() {
this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort());
}
+ // [fallen's fork] player uuid rewrite - lifecycle hook
+ UuidRewriteHooks.onServerStart(this);
+
Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics());
}
@@ -513,6 +516,9 @@ public void shutdown(boolean explicitExit, Component reason) {
player.disconnect(reason);
}
+ // [fallen's fork] player uuid rewrite - lifecycle hook
+ UuidRewriteHooks.onServerStop(this);
+
try {
boolean timedOut = false;
@@ -643,6 +649,10 @@ public boolean registerConnection(ConnectedPlayer connection) {
connectionsByName.put(lowerName, connection);
connectionsByUuid.put(connection.getUniqueId(), connection);
}
+
+ // [fallen's fork] player uuid rewrite - lifecycle hook
+ UuidRewriteHooks.onPlayerConnect(this, connection);
+
return true;
}
@@ -656,8 +666,8 @@ public void unregisterConnection(ConnectedPlayer connection) {
connectionsByUuid.remove(connection.getUniqueId(), connection);
connection.disconnected();
- // [fallen's fork] player uuid rewrite -
- TabListUuidRewriter.onPlayerDisconnect(this, connection);
+ // [fallen's fork] player uuid rewrite - lifecycle hook
+ UuidRewriteHooks.onPlayerDisconnect(this, connection);
}
@Override
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java
index 14cb8caa10..f3261865ae 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java
@@ -86,6 +86,9 @@ public class VelocityConfiguration implements ProxyConfig {
// [fallen's fork] mojang auth proxy
@Expose
private final AuthProxy authProxy;
+ // [fallen's fork] player uuid rewrite
+ @Expose
+ private final UuidRewrite uuidRewrite;
@Expose
private final Query query;
@@ -99,11 +102,13 @@ public class VelocityConfiguration implements ProxyConfig {
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
AuthProxy authProxy, // [fallen's fork] mojang auth proxy
+ UuidRewrite uuidRewrite, // // [fallen's fork] player uuid rewrite
Query query, Metrics metrics) {
this.servers = servers;
this.forcedHosts = forcedHosts;
this.advanced = advanced;
this.authProxy = authProxy; // [fallen's fork] mojang auth proxy
+ this.uuidRewrite = uuidRewrite; // [fallen's fork] player uuid rewrite
this.query = query;
this.metrics = metrics;
}
@@ -115,6 +120,7 @@ private VelocityConfiguration(String bind, String motd, int showMaxPlayers, bool
boolean enablePlayerAddressLogging, Servers servers, ForcedHosts forcedHosts,
Advanced advanced,
AuthProxy authProxy, // [fallen's fork] mojang auth proxy
+ UuidRewrite uuidRewrite, // // [fallen's fork] player uuid rewrite
Query query, Metrics metrics, boolean forceKeyAuthentication) {
this.bind = bind;
this.motd = motd;
@@ -131,6 +137,7 @@ private VelocityConfiguration(String bind, String motd, int showMaxPlayers, bool
this.forcedHosts = forcedHosts;
this.advanced = advanced;
this.authProxy = authProxy; // [fallen's fork] mojang auth proxy
+ this.uuidRewrite = uuidRewrite; // [fallen's fork] player uuid rewrite
this.query = query;
this.metrics = metrics;
this.forceKeyAuthentication = forceKeyAuthentication;
@@ -431,6 +438,24 @@ public int getAuthProxyPort() {
}
// [fallen's fork] mojang auth proxy ends
+ // // [fallen's fork] player uuid rewrite starts
+ public boolean isUuidRewriteEnabled() {
+ return uuidRewrite.isEnabled();
+ }
+
+ public boolean isUuidRewriteDatabaseEnabled() {
+ return uuidRewrite.isDatabaseEnabled();
+ }
+
+ public void setUuidRewriteDatabaseEnabled(boolean b) {
+ uuidRewrite.setDatabaseEnabled(b);
+ }
+
+ public String getUuidRewriteDatabasePath() {
+ return uuidRewrite.getDatabasePath();
+ }
+ // // [fallen's fork] player uuid rewrite ends
+
public boolean isForceKeyAuthentication() {
return forceKeyAuthentication;
}
@@ -524,7 +549,8 @@ public static VelocityConfiguration read(Path path) throws IOException {
final CommentedConfig serversConfig = config.get("servers");
final CommentedConfig forcedHostsConfig = config.get("forced-hosts");
final CommentedConfig advancedConfig = config.get("advanced");
- final CommentedConfig autoProxy = config.get("auth-proxy");
+ final CommentedConfig autoProxy = config.get("auth-proxy"); // [fallen's fork] mojang auth proxy
+ final CommentedConfig uuidRewrite = config.get("uuid-rewrite"); // [fallen's fork] player uuid rewrite
final CommentedConfig queryConfig = config.get("query");
final CommentedConfig metricsConfig = config.get("metrics");
final PlayerInfoForwarding forwardingMode = config.getEnumOrElse(
@@ -566,7 +592,8 @@ public static VelocityConfiguration read(Path path) throws IOException {
new Servers(serversConfig),
new ForcedHosts(forcedHostsConfig),
new Advanced(advancedConfig),
- new AuthProxy(autoProxy),
+ new AuthProxy(autoProxy), // [fallen's fork] mojang auth proxy
+ new UuidRewrite(uuidRewrite), // [fallen's fork] player uuid rewrite
new Query(queryConfig),
new Metrics(metricsConfig),
forceKeyAuthentication
@@ -894,6 +921,42 @@ public int getPort() {
}
}
+ /**
+ * [fallen's fork] player uuid rewrite - config.
+ */
+ private static class UuidRewrite {
+ @Expose
+ private boolean enabled = true;
+ @Expose
+ private boolean databaseEnabled = false;
+ @Expose
+ private String databasePath = "uuid_mapping.db";
+
+ public UuidRewrite(CommentedConfig config) {
+ if (config != null) {
+ this.enabled = config.getOrElse("enabled", true);
+ this.databaseEnabled = config.getOrElse("databaseEnabled", false);
+ this.databasePath = config.getOrElse("databasePath", "uuid_mapping.db");
+ }
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public boolean isDatabaseEnabled() {
+ return databaseEnabled;
+ }
+
+ public void setDatabaseEnabled(boolean databaseEnabled) {
+ this.databaseEnabled = databaseEnabled;
+ }
+
+ public String getDatabasePath() {
+ return databasePath;
+ }
+ }
+
private static class Query {
@Expose
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java
index e2bd4601d4..030f26333a 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java
@@ -66,11 +66,11 @@
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfoPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket;
-import com.velocitypowered.proxy.protocol.packet.uuidrewrite.EntityPacketUuidRewriter;
-import com.velocitypowered.proxy.protocol.packet.uuidrewrite.TabListUuidRewriter;
import com.velocitypowered.proxy.protocol.packet.uuidrewrite.UrSpawnEntityS2CPacket;
import com.velocitypowered.proxy.protocol.packet.uuidrewrite.UrSpawnPlayerS2CPacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
+import com.velocitypowered.proxy.uuidrewrite.EntityPacketUuidRewriter;
+import com.velocitypowered.proxy.uuidrewrite.TabListUuidRewriter;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java
index c259a8a10c..1b78fc3998 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java
@@ -72,10 +72,10 @@
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
-import com.velocitypowered.proxy.protocol.packet.uuidrewrite.EntityPacketUuidRewriter;
import com.velocitypowered.proxy.protocol.packet.uuidrewrite.UrSpectatorTeleportC2SPacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.CharacterUtil;
+import com.velocitypowered.proxy.uuidrewrite.EntityPacketUuidRewriter;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/uuidrewrite/EntityPacketUuidRewriter.java b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/EntityPacketUuidRewriter.java
similarity index 52%
rename from proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/uuidrewrite/EntityPacketUuidRewriter.java
rename to proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/EntityPacketUuidRewriter.java
index 7122884558..99e1c6dfa0 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/uuidrewrite/EntityPacketUuidRewriter.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/EntityPacketUuidRewriter.java
@@ -15,73 +15,59 @@
* along with this program. If not, see .
*/
-package com.velocitypowered.proxy.protocol.packet.uuidrewrite;
+package com.velocitypowered.proxy.uuidrewrite;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.VelocityServer;
-import com.velocitypowered.proxy.config.PlayerInfoForwarding;
+import com.velocitypowered.proxy.protocol.packet.uuidrewrite.PacketToRewriteEntityUuid;
+import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import java.util.UUID;
-import java.util.function.Function;
-
+/**
+ * [fallen's fork] player uuid rewrite - entity packets: rewrite logic.
+ */
public class EntityPacketUuidRewriter {
private static final boolean DEBUG = false;
private static final Logger logger = LogManager.getLogger(EntityPacketUuidRewriter.class);
public static void rewriteS2C(VelocityServer server, Player connectionPlayer, PacketToRewriteEntityUuid packet) {
- rewrite(server, connectionPlayer, packet, Player::getOfflineUuid, Player::getUniqueId);
+ rewrite(server, connectionPlayer, packet, RewriteDirection.S2C);
}
public static void rewriteC2S(VelocityServer server, Player connectionPlayer, PacketToRewriteEntityUuid packet) {
- rewrite(server, connectionPlayer, packet, Player::getUniqueId, Player::getOfflineUuid);
+ rewrite(server, connectionPlayer, packet, RewriteDirection.C2S);
}
private static void rewrite(VelocityServer server, Player connectionPlayer, PacketToRewriteEntityUuid packet,
- Function uuidFrom, Function uuidTo) {
+ RewriteDirection direction) {
if (DEBUG) {
- logger.info("EPUR for {} start, packet {} ({} {})", connectionPlayer.getUsername(), packet.getClass().getSimpleName(), packet.isPlayer(), packet.getEntityUuid());
+ logger.info("EPUR for {} start, packet {} ({} {})", connectionPlayer.getUsername(), packet.getClass().getSimpleName(),
+ packet.isPlayer(), packet.getEntityUuid());
}
- var config = server.getConfiguration();
- if (!(config.isOnlineMode() && config.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE)) {
- return; // early return for performance optimization
- }
- if (!packet.isPlayer()) {
+ if (!UuidRewriteUtils.isUuidRewriteEnabled(server.getConfiguration())) {
return;
}
- UUID uuid = packet.getEntityUuid();
- if (uuid == null) {
+ if (!packet.isPlayer()) {
return;
}
- if (DEBUG) {
- logger.info("EPUR for {} check pass, uuid to rewrite: {}", connectionPlayer.getUsername(), uuid);
+ final UUID oldUuid = packet.getEntityUuid();
+ if (oldUuid == null) {
+ return;
}
- // FIXME: inefficient implementation using for loop. Replace it with map lookup?
+ var rewriter = UuidRewriter.create(server);
for (Player player : server.getAllPlayers()) {
- UUID serverUuid = uuidFrom.apply(player);
- if (DEBUG) {
- logger.info("EPUR for {} checking {} {}", connectionPlayer.getUsername(), player.getUsername(), serverUuid);
- }
- if (serverUuid.equals(uuid)) {
- if (DEBUG) {
- logger.info("EPUR for {} match {}", connectionPlayer.getUsername(), player.getUsername());
- }
-
- UUID newUuid = uuidTo.apply(player);
- if (!newUuid.equals(uuid)) {
+ if (direction.getSourceUuid(player).equals(oldUuid)) {
+ var newUuid = rewriter.rewrite(player, direction);
+ if (newUuid != null && !newUuid.equals(oldUuid)) {
packet.setEntityUuid(newUuid);
}
break;
}
}
-
- if (DEBUG) {
- logger.info("EPUR for {} check end", connectionPlayer.getUsername());
- }
}
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/RewriteDirection.java b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/RewriteDirection.java
new file mode 100644
index 0000000000..e80547f68e
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/RewriteDirection.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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 .
+ */
+
+package com.velocitypowered.proxy.uuidrewrite;
+
+import com.velocitypowered.api.proxy.Player;
+import java.util.UUID;
+import java.util.function.Function;
+
+/**
+ * [fallen's fork] player uuid rewrite - implementation.
+ */
+public enum RewriteDirection {
+ OFFLINE_TO_ONLINE(Player::getOfflineUuid),
+ ONLINE_TO_OFFLINE(Player::getUniqueId);
+
+ public static final RewriteDirection S2C = OFFLINE_TO_ONLINE;
+ public static final RewriteDirection C2S = ONLINE_TO_OFFLINE;
+
+ private final Function uuidExtractor;
+
+ RewriteDirection(Function uuidExtractor) {
+ this.uuidExtractor = uuidExtractor;
+ }
+
+ UUID getSourceUuid(Player player) {
+ return this.uuidExtractor.apply(player);
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/uuidrewrite/TabListUuidRewriter.java b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/TabListUuidRewriter.java
similarity index 76%
rename from proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/uuidrewrite/TabListUuidRewriter.java
rename to proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/TabListUuidRewriter.java
index 97e3a21de6..24b0b9441f 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/uuidrewrite/TabListUuidRewriter.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/TabListUuidRewriter.java
@@ -15,12 +15,11 @@
* along with this program. If not, see .
*/
-package com.velocitypowered.proxy.protocol.packet.uuidrewrite;
+package com.velocitypowered.proxy.uuidrewrite;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.VelocityServer;
-import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
@@ -28,10 +27,7 @@
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfoPacket;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfoPacket;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
import java.util.Optional;
-import java.util.UUID;
import java.util.stream.Collectors;
/**
@@ -41,26 +37,23 @@ public class TabListUuidRewriter {
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private static boolean shouldRewrite(VelocityServer server) {
- var config = server.getConfiguration();
- return config.isOnlineMode() && config.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE;
+ return UuidRewriteUtils.isUuidRewriteEnabled(server.getConfiguration());
}
- // offline / server uuid -> online / client uuid
- private static Map makeUuidMappingView(VelocityServer server) {
- Map view = new HashMap<>();
- for (Player player : server.getAllPlayers()) {
- view.put(player.getOfflineUuid(), player.getUniqueId());
- }
- return view;
- }
-
- // [fallen's fork] player uuid rewrite
- // send the missing player tab-list removal packets to other players in the mc server
- // see bungeecord net.md_5.bungee.connection.UpstreamBridge#disconnected
- public static void onPlayerDisconnect(VelocityServer server, ConnectedPlayer player) {
+ /**
+ * [fallen's fork] player uuid rewrite
+ * send the missing player tab-list removal packets to other players in the mc server
+ * see bungeecord net.md_5.bungee.connection.UpstreamBridge#disconnected
+ */
+ public static void sendRewrittenTabListRemovalPackets(VelocityServer server, ConnectedPlayer player) {
if (!shouldRewrite(server)) {
return;
}
+ if (server.getConfiguration().isUuidRewriteDatabaseEnabled()) {
+ // if the database is enabled, then no need for this bungee hack
+ // cuz the mapping is always available
+ return;
+ }
VelocityServerConnection connectedServer = player.getConnectedServer();
if (connectedServer == null) {
@@ -77,7 +70,7 @@ public static void onPlayerDisconnect(VelocityServer server, ConnectedPlayer pla
for (Player otherPlayer : connectedServer.getServer().getPlayersConnected()) {
if (otherPlayer != player && otherPlayer instanceof ConnectedPlayer) {
- var connection = ((ConnectedPlayer)otherPlayer).getConnection();
+ var connection = ((ConnectedPlayer) otherPlayer).getConnection();
MinecraftPacket packet;
if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
packet = newPacket;
@@ -97,9 +90,9 @@ public static void rewrite(VelocityServer server, LegacyPlayerListItemPacket pac
return;
}
- var uuidMapping = makeUuidMappingView(server);
+ var rewriter = UuidRewriter.create(server);
packet.getItems().replaceAll(item -> {
- var clientSideUuid = uuidMapping.get(item.getUuid());
+ var clientSideUuid = rewriter.toClient(item.getUuid());
if (clientSideUuid != null && !clientSideUuid.equals(item.getUuid())) {
var newItem = new LegacyPlayerListItemPacket.Item(clientSideUuid);
@@ -125,9 +118,9 @@ public static void rewrite(VelocityServer server, UpsertPlayerInfoPacket packet)
return;
}
- var uuidMapping = makeUuidMappingView(server);
+ var rewriter = UuidRewriter.create(server);
packet.getEntries().replaceAll(entry -> {
- var clientSideUuid = uuidMapping.get(entry.getProfileId());
+ var clientSideUuid = rewriter.toClient(entry.getProfileId());
if (clientSideUuid != null && !clientSideUuid.equals(entry.getProfileId())) {
var newEntry = new UpsertPlayerInfoPacket.Entry(clientSideUuid);
@@ -153,9 +146,9 @@ public static void rewrite(VelocityServer server, RemovePlayerInfoPacket packet)
return;
}
- var uuidMapping = makeUuidMappingView(server);
+ var rewriter = UuidRewriter.create(server);
var newProfiles = packet.getProfilesToRemove().stream()
- .map(serverUuid -> Optional.ofNullable(uuidMapping.get(serverUuid)).orElse(serverUuid))
+ .map(serverUuid -> Optional.ofNullable(rewriter.toClient(serverUuid)).orElse(serverUuid))
.collect(Collectors.toList());
packet.setProfilesToRemove(newProfiles);
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidMappingDatabase.java b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidMappingDatabase.java
new file mode 100644
index 0000000000..c691d97b9c
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidMappingDatabase.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024 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 .
+ */
+
+package com.velocitypowered.proxy.uuidrewrite;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.sqlite.SQLiteConfig;
+import org.sqlite.SQLiteDataSource;
+
+/**
+ * [fallen's fork] player uuid rewrite - uuid database.
+ */
+@SuppressWarnings({"MissingJavadocMethod", "MissingJavadocType"})
+public class UuidMappingDatabase {
+
+ private static final Logger logger = LogManager.getLogger(UuidMappingDatabase.class);
+ private static final UuidMappingDatabase INSTANCE = new UuidMappingDatabase();
+ private final SQLiteDataSource dataSource;
+ private Connection connection;
+ private boolean enabled = false;
+
+ private UuidMappingDatabase() {
+ SQLiteConfig config = new SQLiteConfig();
+ config.enforceForeignKeys(true);
+
+ this.dataSource = new SQLiteDataSource(config);
+ }
+
+ public static UuidMappingDatabase getInstance() {
+ return INSTANCE;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ private Connection getConnection() throws SQLException {
+ if (this.connection == null || this.connection.isClosed()) {
+ this.connection = this.dataSource.getConnection();
+ }
+ return this.connection;
+ }
+
+ public void init(String dbPath) throws SQLException {
+ String url = "jdbc:sqlite:" + dbPath;
+ this.dataSource.setUrl(url);
+
+ try (var stmt = this.getConnection().createStatement()) {
+ stmt.executeUpdate(
+ "CREATE TABLE IF NOT EXISTS uuid_mapping ("
+ + "online_uuid TEXT PRIMARY KEY, "
+ + "offline_uuid TEXT, "
+ + "player_name TEXT, "
+ + "updated_at INTEGER)"
+ );
+ stmt.executeUpdate("CREATE INDEX IF NOT EXISTS idx_online_uuid ON uuid_mapping (online_uuid)");
+ stmt.executeUpdate("CREATE INDEX IF NOT EXISTS idx_offline_uuid ON uuid_mapping (offline_uuid)");
+ stmt.executeUpdate("CREATE INDEX IF NOT EXISTS idx_last_used ON uuid_mapping (updated_at)");
+ }
+ }
+
+ public void close() {
+ try {
+ if (this.connection != null && !this.connection.isClosed()) {
+ this.connection.close();
+ }
+ } catch (SQLException sqlException) {
+ logger.error("close failed", sqlException);
+ }
+ }
+
+ @Nullable
+ public UUID queryOnlineUuid(UUID offlineUuid) {
+ if (!this.enabled) {
+ return null;
+ }
+ try {
+ String query = "SELECT online_uuid FROM uuid_mapping WHERE offline_uuid = ?";
+ try (var stmt = this.getConnection().prepareStatement(query)) {
+ stmt.setString(1, offlineUuid.toString());
+ ResultSet resultSet = stmt.executeQuery();
+ if (resultSet.next()) {
+ return UUID.fromString(resultSet.getString("online_uuid"));
+ }
+ }
+ } catch (SQLException sqlException) {
+ logger.error("queryOnlineUuid failed", sqlException);
+ }
+ return null;
+ }
+
+ @Nullable
+ public UUID queryOfflineUuid(UUID onlineUuid) {
+ if (!this.enabled) {
+ return null;
+ }
+ try {
+ String query = "SELECT offline_uuid FROM uuid_mapping WHERE online_uuid = ?";
+ try (var stmt = this.getConnection().prepareStatement(query)) {
+ stmt.setString(1, onlineUuid.toString());
+ ResultSet resultSet = stmt.executeQuery();
+ if (resultSet.next()) {
+ return UUID.fromString(resultSet.getString("offline_uuid"));
+ }
+ }
+ } catch (SQLException sqlException) {
+ logger.error("queryOfflineUuid failed", sqlException);
+ }
+ return null;
+ }
+
+ public void createRow(UUID onlineUuid, UUID offlineUuid, String playerName) {
+ if (!this.enabled) {
+ return;
+ }
+ try {
+ String sql =
+ "INSERT OR REPLACE INTO uuid_mapping (online_uuid, offline_uuid, player_name, updated_at) "
+ + "VALUES (?, ?, ?, strftime('%s','now'))";
+ try (var stmt = this.getConnection().prepareStatement(sql)) {
+ stmt.setString(1, onlineUuid.toString());
+ stmt.setString(2, offlineUuid.toString());
+ stmt.setString(3, playerName);
+ stmt.executeUpdate();
+ }
+ } catch (SQLException sqlException) {
+ logger.error("createRow failed", sqlException);
+ }
+ }
+
+ // TODO
+ public void deleteExpiredRows(long thresholdInSeconds) {
+ if (!this.enabled) {
+ return;
+ }
+ try {
+ String sql = "DELETE FROM uuid_mapping WHERE strftime('%s','now') - updated_at > ?";
+ try (var stmt = this.getConnection().prepareStatement(sql)) {
+ stmt.setLong(1, thresholdInSeconds);
+ stmt.executeUpdate();
+ }
+ } catch (SQLException sqlException) {
+ logger.error("deleteExpiredRows failed", sqlException);
+ }
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidRewriteHooks.java b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidRewriteHooks.java
new file mode 100644
index 0000000000..ec963c7642
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidRewriteHooks.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 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 .
+ */
+
+package com.velocitypowered.proxy.uuidrewrite;
+
+import com.velocitypowered.proxy.VelocityServer;
+import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
+import java.sql.SQLException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * [fallen's fork] player uuid rewrite - lifecycle hooks.
+ */
+@SuppressWarnings("MissingJavadocMethod")
+public class UuidRewriteHooks {
+
+ private static final Logger logger = LogManager.getLogger(UuidRewriteHooks.class);
+ private static final UuidMappingDatabase db = UuidMappingDatabase.getInstance();
+
+ public static void onServerStart(VelocityServer server) {
+ var config = server.getConfiguration();
+ if (config.isUuidRewriteDatabaseEnabled()) {
+ var dbPath = config.getUuidRewriteDatabasePath();
+ try {
+ db.init(dbPath);
+ logger.info("UUID-Rewrite database connect ok, path '{}'", dbPath);
+ } catch (SQLException e) {
+ logger.error("UUID-Rewrite database initialization failed, disabling database, path '{}'", dbPath, e);
+ config.setUuidRewriteDatabaseEnabled(false);
+ }
+ }
+ db.setEnabled(config.isUuidRewriteDatabaseEnabled());
+ }
+
+ public static void onServerStop(VelocityServer server) {
+ var config = server.getConfiguration();
+ if (config.isUuidRewriteDatabaseEnabled()) {
+ db.close();
+ }
+ }
+
+ public static void onPlayerConnect(VelocityServer server, ConnectedPlayer player) {
+ var config = server.getConfiguration();
+ if (config.isUuidRewriteDatabaseEnabled()) {
+ db.createRow(player.getUniqueId(), player.getOfflineUuid(), player.getUsername());
+ }
+ }
+
+ public static void onPlayerDisconnect(VelocityServer server, ConnectedPlayer player) {
+ TabListUuidRewriter.sendRewrittenTabListRemovalPackets(server, player);
+ }
+
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidRewriteUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidRewriteUtils.java
new file mode 100644
index 0000000000..51236e3b56
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidRewriteUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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 .
+ */
+
+package com.velocitypowered.proxy.uuidrewrite;
+
+import com.velocitypowered.proxy.config.PlayerInfoForwarding;
+import com.velocitypowered.proxy.config.VelocityConfiguration;
+
+/**
+ * [fallen's fork] player uuid rewrite - implementation.
+ */
+@SuppressWarnings("MissingJavadocMethod")
+public class UuidRewriteUtils {
+
+ public static boolean isUuidRewriteEnabled(VelocityConfiguration config) {
+ if (config.isUuidRewriteEnabled()) {
+ return config.isOnlineMode() && config.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE;
+ }
+ return false;
+ }
+
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidRewriter.java b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidRewriter.java
new file mode 100644
index 0000000000..6ba15d9772
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/uuidrewrite/UuidRewriter.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 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 .
+ */
+
+package com.velocitypowered.proxy.uuidrewrite;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.velocitypowered.api.proxy.Player;
+import com.velocitypowered.proxy.VelocityServer;
+import java.util.UUID;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * offline / server UUID <-> online / client UUID.
+ */
+@SuppressWarnings({"MissingJavadocMethod", "MissingJavadocType"})
+public interface UuidRewriter {
+
+ // -------------------- Interfaces --------------------
+
+ @Nullable UUID toOnline(UUID offlineUuid);
+
+ @Nullable UUID toOffline(UUID onlineUuid);
+
+ default @Nullable UUID toClient(UUID serverUuid) {
+ return this.toOnline(serverUuid);
+ }
+
+ default @Nullable UUID toServer(UUID clientUuid) {
+ return this.toOffline(clientUuid);
+ }
+
+ default @Nullable UUID rewrite(UUID uuid, RewriteDirection direction) {
+ return switch (direction) {
+ case ONLINE_TO_OFFLINE -> this.toOffline(uuid);
+ case OFFLINE_TO_ONLINE -> this.toOnline(uuid);
+ };
+ }
+
+ default @Nullable UUID rewrite(Player player, RewriteDirection direction) {
+ return this.rewrite(direction.getSourceUuid(player), direction);
+ }
+
+ // -------------------- Utilities --------------------
+
+ static UuidRewriter create(VelocityServer server) {
+ return new ChainedRewriter(new MapRewriter(server), new DatabaseRewriter());
+ }
+
+ // -------------------- Implementations --------------------
+
+ class MapRewriter implements UuidRewriter {
+ private final BiMap offlineToOnline = HashBiMap.create();
+
+ private MapRewriter(VelocityServer server) {
+ for (Player player : server.getAllPlayers()) {
+ this.offlineToOnline.put(player.getOfflineUuid(), player.getUniqueId());
+ }
+ }
+
+ @Override
+ public @Nullable UUID toOnline(UUID offlineUuid) {
+ return this.offlineToOnline.get(offlineUuid);
+ }
+
+ @Override
+ public @Nullable UUID toOffline(UUID onlineUuid) {
+ return this.offlineToOnline.inverse().get(onlineUuid);
+ }
+ }
+
+ class DatabaseRewriter implements UuidRewriter {
+ private DatabaseRewriter() {}
+
+ @Override
+ public @Nullable UUID toOnline(UUID offlineUuid) {
+ var db = UuidMappingDatabase.getInstance();
+ return db.queryOnlineUuid(offlineUuid);
+ }
+
+ @Override
+ public @Nullable UUID toOffline(UUID onlineUuid) {
+ var db = UuidMappingDatabase.getInstance();
+ return db.queryOfflineUuid(onlineUuid);
+ }
+ }
+
+ class ChainedRewriter implements UuidRewriter {
+ private final UuidRewriter[] rewriters;
+
+ private ChainedRewriter(UuidRewriter... rewriters) {
+ this.rewriters = rewriters;
+ }
+
+ @Override
+ public @Nullable UUID toOnline(UUID offlineUuid) {
+ for (UuidRewriter rewriter : this.rewriters) {
+ var result = rewriter.toOnline(offlineUuid);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public @Nullable UUID toOffline(UUID onlineUuid) {
+ for (UuidRewriter rewriter : this.rewriters) {
+ var result = rewriter.toOffline(onlineUuid);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/proxy/src/main/resources/default-velocity.toml b/proxy/src/main/resources/default-velocity.toml
index 7dd1868ba5..79a96ce620 100644
--- a/proxy/src/main/resources/default-velocity.toml
+++ b/proxy/src/main/resources/default-velocity.toml
@@ -153,6 +153,13 @@ type = "http"
hostname = "127.0.0.1"
port = 1081
+# [fallen's fork] player uuid rewrite
+# See readme for more information
+[uuid-rewrite]
+enabled = true
+databaseEnabled = false
+databasePath = "uuid_mapping.db"
+
[query]
# Whether to enable responding to GameSpy 4 query responses or not.
enabled = false