This is generated by calude As it's only change the display, no tests and review are required
204 lines
14 KiB
Diff
204 lines
14 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: MrHua269 <mrhua269@gmail.com>
|
|
Date: Sat, 27 Jun 2026 17:23:22 +0800
|
|
Subject: [PATCH] Leaf Configurable vanilla username check
|
|
|
|
A part of Leaf(https://github.com/Winds-Studio/Leaf/blob/edb0504069139beaa6f39efa4702370c2576b3fc/leaf-server/minecraft-patches/features/0086-Configurable-vanilla-username-check.patch)
|
|
|
|
License: https://github.com/Winds-Studio/Leaf/blob/edb0504069139beaa6f39efa4702370c2576b3fc/LICENSE.md
|
|
|
|
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
|
index ab8d69fa54203175760d394a5425601073fcd0d9..6d5beffa1a771182d717f2baab5c626b4970c203 100644
|
|
--- a/net/minecraft/server/MinecraftServer.java
|
|
+++ b/net/minecraft/server/MinecraftServer.java
|
|
@@ -217,7 +217,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
public static final NameAndId ANONYMOUS_PLAYER_PROFILE = new NameAndId(Util.NIL_UUID, "Anonymous Player");
|
|
public static final String SERVER_THREAD_NAME = "Server thread";
|
|
public LevelStorageSource.LevelStorageAccess storageSource;
|
|
- protected final PlayerDataStorage playerDataStorage;
|
|
+ public final PlayerDataStorage playerDataStorage;
|
|
private final SavedDataStorage savedDataStorage;
|
|
private final List<Runnable> tickables = Lists.newArrayList();
|
|
// Paper - per-level GameRules
|
|
diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
|
|
index 7de3bbecea2f11e4e1cb65408729f346760dc9b8..b72b81849c7fedad8efc5c7b112e89eb01dc74dd 100644
|
|
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
|
|
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
|
|
@@ -167,11 +167,20 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
|
|
public void handleHello(final ServerboundHelloPacket packet) {
|
|
Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet");
|
|
// Paper start - Validate usernames
|
|
- if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()
|
|
+ // Leaf start - Configurable vanilla username check
|
|
+ boolean allPrevChecksPassed;
|
|
+ if (moe.monnairealms.camellia.config.modules.misc.UsernameCheckConfig.enabled
|
|
+ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()
|
|
&& io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation
|
|
&& !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) {
|
|
- Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username");
|
|
+ allPrevChecksPassed = true;
|
|
+ if (!moe.monnairealms.camellia.config.modules.misc.UsernameCheckConfig.allowOldPlayersJoin) {
|
|
+ Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username");
|
|
+ }
|
|
+ } else {
|
|
+ allPrevChecksPassed = false;
|
|
}
|
|
+ // Leaf end - Configurable vanilla username check
|
|
this.requestedUuid = packet.profileId();
|
|
// Paper end - Validate usernames
|
|
this.requestedUsername = packet.name();
|
|
@@ -197,6 +206,15 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
|
|
authenticatorPool.execute(() -> {
|
|
try {
|
|
GameProfile gameprofile = ServerLoginPacketListenerImpl.this.createOfflineProfile(ServerLoginPacketListenerImpl.this.requestedUsername); // Spigot
|
|
+ // Leaf start - Configurable vanilla username check
|
|
+ if (moe.monnairealms.camellia.config.modules.misc.UsernameCheckConfig.allowOldPlayersJoin) {
|
|
+ if (server.playerDataStorage.load(new net.minecraft.server.players.NameAndId(gameprofile)).orElse(null) != null) {
|
|
+ server.getPlayerList().playedPlayers.add(packet.name());
|
|
+ } else if (allPrevChecksPassed) {
|
|
+ Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username");
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - Configurable vanilla username check
|
|
|
|
gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent
|
|
ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.name(), gameprofile.id());
|
|
@@ -341,7 +359,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
|
|
server.getPluginManager().callEvent(asyncEvent);
|
|
profile = asyncEvent.getPlayerProfile();
|
|
profile.complete(true); // Paper - setPlayerProfileAPI
|
|
- gameprofile = com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile);
|
|
+ gameprofile = com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopyCustomValidation(profile); // Leaf - Configurable vanilla username check
|
|
playerName = gameprofile.name();
|
|
uniqueId = gameprofile.id();
|
|
// Paper end - Add more fields to AsyncPlayerPreLoginEvent
|
|
diff --git a/net/minecraft/server/players/CachedUserNameToIdResolver.java b/net/minecraft/server/players/CachedUserNameToIdResolver.java
|
|
index 7443744e3f256983e52a1cefcaf66082e4a46a7b..b7553611723f7120c66e1f3e51d89df28bd2756b 100644
|
|
--- a/net/minecraft/server/players/CachedUserNameToIdResolver.java
|
|
+++ b/net/minecraft/server/players/CachedUserNameToIdResolver.java
|
|
@@ -67,7 +67,7 @@ public class CachedUserNameToIdResolver implements UserNameToIdResolver {
|
|
}
|
|
|
|
private Optional<NameAndId> lookupGameProfile(final GameProfileRepository profileRepository, final String name) {
|
|
- if (!StringUtil.isValidPlayerName(name)) {
|
|
+ if (!StringUtil.isValidPlayerName(name, false)) { // Leaf - Configurable vanilla username check - Directly return, skip unnecessary following logic
|
|
return this.createUnknownProfile(name);
|
|
}
|
|
|
|
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
|
|
index 134d74d8ed2c61b28cda0bd5107dea88bc114f51..0392962ed409e3f970904de05926c356610f0e4c 100644
|
|
--- a/net/minecraft/server/players/PlayerList.java
|
|
+++ b/net/minecraft/server/players/PlayerList.java
|
|
@@ -134,6 +134,7 @@ public abstract class PlayerList {
|
|
private org.bukkit.craftbukkit.CraftServer cserver;
|
|
private final Map<String,ServerPlayer> playersByName = new java.util.HashMap<>();
|
|
public @Nullable String collideRuleTeamName; // Paper - Configurable player collision
|
|
+ public final List<String> playedPlayers = new java.util.concurrent.CopyOnWriteArrayList<>(); // Leaf - Configurable vanilla username check
|
|
|
|
// Folia start - region threading
|
|
private final Object connectionsStateLock = new Object();
|
|
@@ -581,6 +582,7 @@ public abstract class PlayerList {
|
|
player.getAdvancements().clearTriggers();
|
|
this.players.remove(player);
|
|
this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
|
|
+ if (moe.monnairealms.camellia.config.modules.misc.UsernameCheckConfig.allowOldPlayersJoin) this.playedPlayers.remove(player.getGameProfile().name()); // Leaf - Configurable vanilla username check
|
|
this.server.getCustomBossEvents().onPlayerDisconnect(player);
|
|
UUID uuid = player.getUUID();
|
|
ServerPlayer serverPlayer = this.playersByUUID.get(uuid);
|
|
diff --git a/net/minecraft/util/StringUtil.java b/net/minecraft/util/StringUtil.java
|
|
index 7957e0cfc43909c5268698a11c4933d20b4d9155..1cee7f26a5cd55b146b89da5912952d16269d77e 100644
|
|
--- a/net/minecraft/util/StringUtil.java
|
|
+++ b/net/minecraft/util/StringUtil.java
|
|
@@ -64,6 +64,15 @@ public class StringUtil {
|
|
}
|
|
|
|
public static boolean isValidPlayerName(final String name) {
|
|
+ // Leaf start - Configurable vanilla username check
|
|
+ return isValidPlayerName(name, moe.monnairealms.camellia.config.modules.misc.UsernameCheckConfig.shouldSkipNonPlayerNameCheck());
|
|
+ }
|
|
+ public static boolean isValidPlayerNameVanilla(final String name) {
|
|
+ return name.length() <= 16 && name.chars().filter(i -> i <= 32 || i >= 127).findAny().isEmpty();
|
|
+ }
|
|
+ public static boolean isValidPlayerName(final String name, final boolean bypassCheck) {
|
|
+ if (bypassCheck) return name.length() <= 16;
|
|
+ // Leaf end - Configurable vanilla username check
|
|
return name.length() <= 16 && name.chars().filter(c -> c <= 32 || c >= 127).findAny().isEmpty();
|
|
}
|
|
|
|
@@ -87,6 +96,12 @@ public class StringUtil {
|
|
|
|
// Paper start - Username validation
|
|
public static boolean isReasonablePlayerName(final String name) {
|
|
+ // Leaf start - Configurable vanilla username check
|
|
+ if (moe.monnairealms.camellia.config.modules.misc.UsernameCheckConfig.allowOldPlayersJoin && net.minecraft.server.MinecraftServer.getServer().getPlayerList().playedPlayers.contains(name)) return true;
|
|
+ if (moe.monnairealms.camellia.config.modules.misc.UsernameCheckConfig.useCustomUsernameRegex()) {
|
|
+ return moe.monnairealms.camellia.config.modules.misc.UsernameCheckConfig.usernameRegex.matcher(name).matches() && name.length() <= 16; // Leaf - Configurable username check
|
|
+ }
|
|
+ // Leaf end - Configurable vanilla username check
|
|
if (name.isEmpty() || name.length() > 16) {
|
|
return false;
|
|
}
|
|
diff --git a/net/minecraft/world/item/component/ResolvableProfile.java b/net/minecraft/world/item/component/ResolvableProfile.java
|
|
index f1b62a6e0d8176d8ba1875e78cc730ee55b1b0c1..c0329b8304cce7ecfe53b102027f73bf4bbf64de 100644
|
|
--- a/net/minecraft/world/item/component/ResolvableProfile.java
|
|
+++ b/net/minecraft/world/item/component/ResolvableProfile.java
|
|
@@ -68,6 +68,30 @@ public abstract sealed class ResolvableProfile implements TooltipProvider permit
|
|
|
|
public abstract Either<GameProfile, ResolvableProfile.Partial> unpack();
|
|
|
|
+ // Leaf start - Configurable vanilla username check - Enforce skull validation
|
|
+ private static Either<String, UUID> sanitizeDynamicPlayerName(final Either<String, UUID> nameOrId) {
|
|
+ if (nameOrId.left().isEmpty()) {
|
|
+ return nameOrId;
|
|
+ }
|
|
+ return moe.monnairealms.camellia.config.modules.misc.UsernameCheckConfig.enforceSkullValidation && !net.minecraft.util.StringUtil.isValidPlayerNameVanilla(nameOrId.left().get()) ? Either.left("INVALID_OWNER") : nameOrId;
|
|
+ }
|
|
+
|
|
+ private static Optional<String> sanitizePartialPlayerName(final Optional<String> name) {
|
|
+ if (name.isEmpty()) {
|
|
+ return name;
|
|
+ }
|
|
+ return moe.monnairealms.camellia.config.modules.misc.UsernameCheckConfig.enforceSkullValidation && !net.minecraft.util.StringUtil.isValidPlayerNameVanilla(name.get()) ? Optional.of("INVALID_OWNER") : name;
|
|
+ }
|
|
+
|
|
+ private static Either<GameProfile, ResolvableProfile.Partial> sanitizeStaticPlayerName(final Either<GameProfile, ResolvableProfile.Partial> contents) {
|
|
+ if (contents.left().isEmpty()) {
|
|
+ return contents;
|
|
+ }
|
|
+ GameProfile gameProfile = contents.left().get();
|
|
+ return moe.monnairealms.camellia.config.modules.misc.UsernameCheckConfig.enforceSkullValidation && !net.minecraft.util.StringUtil.isValidPlayerNameVanilla(gameProfile.name()) ? Either.left(new GameProfile(gameProfile.id(), "INVALID_OWNER", gameProfile.properties())) : contents;
|
|
+ }
|
|
+ // Leaf end - Configurable vanilla username check - Enforce skull validation
|
|
+
|
|
protected ResolvableProfile(final GameProfile partialProfile, final PlayerSkin.Patch skinPatch) {
|
|
this.partialProfile = partialProfile;
|
|
this.skinPatch = skinPatch;
|
|
@@ -96,6 +120,7 @@ public abstract sealed class ResolvableProfile implements TooltipProvider permit
|
|
private final Either<String, UUID> nameOrId;
|
|
|
|
public Dynamic(final Either<String, UUID> nameOrId, final PlayerSkin.Patch skinPatch) {
|
|
+ sanitizeDynamicPlayerName(nameOrId); // Leaf - Configurable vanilla username check
|
|
super(ResolvableProfile.createPartialProfile(nameOrId.left(), nameOrId.right(), PropertyMap.EMPTY), skinPatch);
|
|
this.nameOrId = nameOrId;
|
|
}
|
|
@@ -135,6 +160,11 @@ public abstract sealed class ResolvableProfile implements TooltipProvider permit
|
|
}
|
|
|
|
public record Partial(Optional<String> name, Optional<UUID> id, PropertyMap properties) {
|
|
+ // Leaf start - Configurable vanilla username check
|
|
+ public Partial {
|
|
+ name = ResolvableProfile.sanitizePartialPlayerName(name);
|
|
+ }
|
|
+ // Leaf end - Configurable vanilla username check
|
|
public static final ResolvableProfile.Partial EMPTY = new ResolvableProfile.Partial(Optional.empty(), Optional.empty(), PropertyMap.EMPTY);
|
|
public static final MapCodec<ResolvableProfile.Partial> MAP_CODEC = RecordCodecBuilder.mapCodec(
|
|
i -> i.group(
|
|
@@ -165,6 +195,7 @@ public abstract sealed class ResolvableProfile implements TooltipProvider permit
|
|
private final Either<GameProfile, ResolvableProfile.Partial> contents;
|
|
|
|
public Static(final Either<GameProfile, ResolvableProfile.Partial> contents, final PlayerSkin.Patch skinPatch) {
|
|
+ sanitizeStaticPlayerName(contents); // Leaf - Configurable vanilla username check
|
|
super(contents.map(gameProfile -> (GameProfile)gameProfile, ResolvableProfile.Partial::createProfile), skinPatch);
|
|
this.contents = contents;
|
|
}
|