2026-07-09 00:03:09 +08:00
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
2026-08-08 11:46:48 +08:00
index c4dddd5c66098301de2abc2de93d4409cd14531d..24ccb47605427f83bbfe552edf4d92e9c16e2a1f 100644
2026-07-09 00:03:09 +08:00
--- 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
2026-07-14 15:09:17 +08:00
index 1b4229085f9d4189efb20c083bbfe2e6712e72a4..822e69a975b5c08547b17f8abfa0d512c70a2625 100644
2026-07-09 00:03:09 +08:00
--- 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;
2026-07-14 12:22:17 +08:00
+ if (io.nanachiyo0721.shiroha.config.modules.misc.UsernameCheckConfig.enabled
2026-07-09 00:03:09 +08:00
+ && 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;
2026-07-14 12:22:17 +08:00
+ if (!io.nanachiyo0721.shiroha.config.modules.misc.UsernameCheckConfig.allowOldPlayersJoin) {
2026-07-09 00:03:09 +08:00
+ 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
2026-07-14 12:22:17 +08:00
+ if (io.nanachiyo0721.shiroha.config.modules.misc.UsernameCheckConfig.allowOldPlayersJoin) {
2026-07-09 00:03:09 +08:00
+ 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
2026-08-08 11:46:48 +08:00
index 7ee90d4a8d333c253077871cede3fed63ef23539..dffb0a5cbbcf7a225402bd2e364941b5271a2c24 100644
2026-07-09 00:03:09 +08:00
--- 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
2026-07-14 12:22:17 +08:00
+ if (io.nanachiyo0721.shiroha.config.modules.misc.UsernameCheckConfig.allowOldPlayersJoin) this.playedPlayers.remove(player.getGameProfile().name()); // Leaf - Configurable vanilla username check
2026-07-09 00:03:09 +08:00
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
2026-07-14 15:09:17 +08:00
index 7957e0cfc43909c5268698a11c4933d20b4d9155..3d87f68561a08bc64d3f5a6efb6498984fc84250 100644
2026-07-09 00:03:09 +08:00
--- 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
2026-07-14 12:22:17 +08:00
+ return isValidPlayerName(name, io.nanachiyo0721.shiroha.config.modules.misc.UsernameCheckConfig.shouldSkipNonPlayerNameCheck());
2026-07-09 00:03:09 +08:00
+ }
+ 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
2026-07-14 12:22:17 +08:00
+ 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
2026-07-09 00:03:09 +08:00
+ }
+ // 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
2026-07-14 15:09:17 +08:00
index f1b62a6e0d8176d8ba1875e78cc730ee55b1b0c1..36fc92246c4afce7804eb013829a41fbda3610f4 100644
2026-07-09 00:03:09 +08:00
--- 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;
+ }
2026-07-14 12:22:17 +08:00
+ return io.nanachiyo0721.shiroha.config.modules.misc.UsernameCheckConfig.enforceSkullValidation && !net.minecraft.util.StringUtil.isValidPlayerNameVanilla(nameOrId.left().get()) ? Either.left("INVALID_OWNER") : nameOrId;
2026-07-09 00:03:09 +08:00
+ }
+
+ private static Optional<String> sanitizePartialPlayerName(final Optional<String> name) {
+ if (name.isEmpty()) {
+ return name;
+ }
2026-07-14 12:22:17 +08:00
+ return io.nanachiyo0721.shiroha.config.modules.misc.UsernameCheckConfig.enforceSkullValidation && !net.minecraft.util.StringUtil.isValidPlayerNameVanilla(name.get()) ? Optional.of("INVALID_OWNER") : name;
2026-07-09 00:03:09 +08:00
+ }
+
+ private static Either<GameProfile, ResolvableProfile.Partial> sanitizeStaticPlayerName(final Either<GameProfile, ResolvableProfile.Partial> contents) {
+ if (contents.left().isEmpty()) {
+ return contents;
+ }
+ GameProfile gameProfile = contents.left().get();
2026-07-14 12:22:17 +08:00
+ 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;
2026-07-09 00:03:09 +08:00
+ }
+ // 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;
}