From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MrHua269 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 3466634dd5305657534b3e80ca25b016a4b40dee..68d2fce6ab5565a0ad671d97e5732c29feacda66 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -217,7 +217,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop tickables = Lists.newArrayList(); // Paper - per-level GameRules diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java index 1b4229085f9d4189efb20c083bbfe2e6712e72a4..822e69a975b5c08547b17f8abfa0d512c70a2625 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 (io.nanachiyo0721.shiroha.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 (!io.nanachiyo0721.shiroha.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 (io.nanachiyo0721.shiroha.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 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 d9e22ca839e6ed756ff15597ba639113342d15e4..af00f2f278f81d3ab156bc48161d454c981bc084 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 playersByName = new java.util.HashMap<>(); public @Nullable String collideRuleTeamName; // Paper - Configurable player collision + public final List 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 (io.nanachiyo0721.shiroha.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..3d87f68561a08bc64d3f5a6efb6498984fc84250 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, io.nanachiyo0721.shiroha.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 (io.nanachiyo0721.shiroha.config.modules.misc.UsernameCheckConfig.allowOldPlayersJoin && net.minecraft.server.MinecraftServer.getServer().getPlayerList().playedPlayers.contains(name)) return true; + if (io.nanachiyo0721.shiroha.config.modules.misc.UsernameCheckConfig.useCustomUsernameRegex()) { + return io.nanachiyo0721.shiroha.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..36fc92246c4afce7804eb013829a41fbda3610f4 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 unpack(); + // Leaf start - Configurable vanilla username check - Enforce skull validation + private static Either sanitizeDynamicPlayerName(final Either nameOrId) { + if (nameOrId.left().isEmpty()) { + return nameOrId; + } + return io.nanachiyo0721.shiroha.config.modules.misc.UsernameCheckConfig.enforceSkullValidation && !net.minecraft.util.StringUtil.isValidPlayerNameVanilla(nameOrId.left().get()) ? Either.left("INVALID_OWNER") : nameOrId; + } + + private static Optional sanitizePartialPlayerName(final Optional name) { + if (name.isEmpty()) { + return name; + } + return io.nanachiyo0721.shiroha.config.modules.misc.UsernameCheckConfig.enforceSkullValidation && !net.minecraft.util.StringUtil.isValidPlayerNameVanilla(name.get()) ? Optional.of("INVALID_OWNER") : name; + } + + private static Either sanitizeStaticPlayerName(final Either contents) { + if (contents.left().isEmpty()) { + return contents; + } + GameProfile gameProfile = contents.left().get(); + return io.nanachiyo0721.shiroha.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 nameOrId; public Dynamic(final Either 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 name, Optional 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 MAP_CODEC = RecordCodecBuilder.mapCodec( i -> i.group( @@ -165,6 +195,7 @@ public abstract sealed class ResolvableProfile implements TooltipProvider permit private final Either contents; public Static(final Either 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; }