From 8fda84ea96a5f93e2f26885db7b8735dcd888e5c Mon Sep 17 00:00:00 2001 From: Creeam <102713261+HaHaWTH@users.noreply.github.com> Date: Wed, 31 Dec 2025 03:41:38 +1400 Subject: [PATCH] Fix configurable username check bugs (#572) * Fix configurable username check bugs * Format * cleanup config load logic --- ...-Configurable-vanilla-username-check.patch | 75 ++++++++++-------- .../features/0110-Leaves-Replay-Mod-API.patch | 4 +- .../0229-Async-switch-connection-state.patch | 4 +- ...-Configurable-vanilla-username-check.patch | 78 +++++++++++++++++++ .../modules/misc/VanillaUsernameCheck.java | 37 ++++++++- 5 files changed, 162 insertions(+), 36 deletions(-) create mode 100644 leaf-server/paper-patches/features/0061-Configurable-vanilla-username-check.patch diff --git a/leaf-server/minecraft-patches/features/0096-Configurable-vanilla-username-check.patch b/leaf-server/minecraft-patches/features/0096-Configurable-vanilla-username-check.patch index 8113a8cf..6666067c 100644 --- a/leaf-server/minecraft-patches/features/0096-Configurable-vanilla-username-check.patch +++ b/leaf-server/minecraft-patches/features/0096-Configurable-vanilla-username-check.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Configurable vanilla username check diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index fb585637383db4592f97f0c9040ffa86afb43c6a..29417442b9379779a078e8fc819035120ddf0108 100644 +index fb585637383db4592f97f0c9040ffa86afb43c6a..5381986c9d283d3b22c59d80acf246dab99771c5 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -2466,7 +2466,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @@ -13,50 +13,64 @@ index fb585637383db4592f97f0c9040ffa86afb43c6a..29417442b9379779a078e8fc81903512 String scoreboardName = getScoreboardName(); String playerListName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().serialize(getBukkitEntity().playerListName()); - String[] split = playerListName.split(scoreboardName); -+ String[] split = playerListName.split(java.util.regex.Pattern.quote(scoreboardName)); // Leaf - Vanilla username check - Sanitize name input ++ String[] split = playerListName.split(java.util.regex.Pattern.quote(scoreboardName)); // Leaf - Configurable vanilla username check - Sanitize name input String prefix = (split.length > 0 ? split[0] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix, ""); String suffix = (split.length > 1 ? split[1] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, ""); if (afk) { diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 6d320ed179393e47398c44f2ba2b2285016f349e..0e7a4d6a3b33a8bd9eebd854b87af269cbec6ea6 100644 +index 6d320ed179393e47398c44f2ba2b2285016f349e..19a56f8c980f739bff54fb067ccf293286f16faf 100644 --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -163,10 +163,15 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, +@@ -163,11 +163,20 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, public void handleHello(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() -+ if (!org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.removeAllCheck // Leaf - Remove vanilla username check ++ // Leaf start - Configurable vanilla username check ++ boolean allPrevChecksPassed; ++ if (!org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.removeAllCheck + && 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"); -+ // Leaf start - Vanilla username check ++ allPrevChecksPassed = true; + if (!org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.allowOldPlayersJoin) { + Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username"); + } -+ // Leaf end - Vanilla username check ++ } else { ++ allPrevChecksPassed = false; } ++ // Leaf end - Configurable vanilla username check this.requestedUuid = packet.profileId(); // Paper end - Validate usernames -@@ -194,6 +199,15 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + this.requestedUsername = packet.name(); +@@ -194,6 +203,15 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, authenticatorPool.execute(() -> { try { GameProfile gameprofile = ServerLoginPacketListenerImpl.this.createOfflineProfile(ServerLoginPacketListenerImpl.this.requestedUsername); // Spigot -+ // Leaf start - Vanilla username check ++ // Leaf start - Configurable vanilla username check + if (org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.allowOldPlayersJoin) { + if (server.playerDataStorage.load(gameprofile.getName(), gameprofile.getId().toString(), gameprofile.getId(), net.minecraft.util.ProblemReporter.DISCARDING).orElse(null) != null) { + server.getPlayerList().playedPlayers.add(packet.name()); -+ } else { ++ } else if (allPrevChecksPassed) { + Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username"); + } + } -+ // Leaf end - Vanilla username check ++ // 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.getName(), gameprofile.getId()); +@@ -335,7 +353,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.getName(); + uniqueId = gameprofile.getId(); + // Paper end - Add more fields to AsyncPlayerPreLoginEvent diff --git a/net/minecraft/server/players/GameProfileCache.java b/net/minecraft/server/players/GameProfileCache.java -index 066f84df5c31242ab542932f1e243369d0e766e2..4faa87d18c2e55d6320f9e8ec91862b9e58d26b5 100644 +index 066f84df5c31242ab542932f1e243369d0e766e2..223f39d2e6d82815af14d437a8c886ae591be597 100644 --- a/net/minecraft/server/players/GameProfileCache.java +++ b/net/minecraft/server/players/GameProfileCache.java @@ -75,7 +75,7 @@ public class GameProfileCache { @@ -64,19 +78,19 @@ index 066f84df5c31242ab542932f1e243369d0e766e2..4faa87d18c2e55d6320f9e8ec91862b9 private static Optional lookupGameProfile(GameProfileRepository profileRepo, String name) { - if (!StringUtil.isValidPlayerName(name)) { -+ if (!StringUtil.isValidPlayerName(name, false)) { // Leaf start - Remove vanilla username check - Directly return, skip unnecessary following logic ++ if (!StringUtil.isValidPlayerName(name, false)) { // Leaf - Configurable vanilla username check - Directly return, skip unnecessary following logic return createUnknownProfile(name); } else { final boolean shouldLookup = !org.apache.commons.lang3.StringUtils.isBlank(name) // Paper - Don't lookup a profile with a blank name diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index 6396083993d248683b887774d2db3f3f03825033..fbf4e548f4377c7155a3a02c3b679a0322ed6bc0 100644 +index 6396083993d248683b887774d2db3f3f03825033..883869d39319e0f98c24d356beab2705d2fa769c 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -136,6 +136,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 - Vanilla username check ++ public final List playedPlayers = new java.util.concurrent.CopyOnWriteArrayList<>(); // Leaf - Configurable vanilla username check public PlayerList(MinecraftServer server, LayeredRegistryAccess registries, PlayerDataStorage playerIo, int maxPlayers) { this.cserver = server.server = new org.bukkit.craftbukkit.CraftServer((net.minecraft.server.dedicated.DedicatedServer) server, this); @@ -84,61 +98,60 @@ index 6396083993d248683b887774d2db3f3f03825033..fbf4e548f4377c7155a3a02c3b679a03 player.getAdvancements().stopListening(); this.players.remove(player); this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot -+ if (org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.allowOldPlayersJoin) this.playedPlayers.remove(player.getGameProfile().getName()); // Leaf - Vanilla username check ++ if (org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.allowOldPlayersJoin) this.playedPlayers.remove(player.getGameProfile().getName()); // Leaf - Configurable vanilla username check this.removeFromSendAllPlayerInfoBuckets(player); // Gale - Purpur - spread out sending all player info this.server.getCustomBossEvents().onPlayerDisconnect(player); UUID uuid = player.getUUID(); diff --git a/net/minecraft/util/StringUtil.java b/net/minecraft/util/StringUtil.java -index c3a99fe7b49858bc0ca9a7f800b0db40465f6901..2f8a5d0b94b548dfc62f2c40bf10218d34c17574 100644 +index c3a99fe7b49858bc0ca9a7f800b0db40465f6901..d344857a20bfed20d8ffd422c0e6f2bd96b2ae8f 100644 --- a/net/minecraft/util/StringUtil.java +++ b/net/minecraft/util/StringUtil.java -@@ -64,6 +64,16 @@ public class StringUtil { +@@ -64,6 +64,15 @@ public class StringUtil { } public static boolean isValidPlayerName(String playerName) { -+ // Leaf start - Remove vanilla username check -+ return isValidPlayerName(playerName, org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.removeAllCheck); ++ // Leaf start - Configurable vanilla username check ++ return isValidPlayerName(playerName, org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.shouldSkipNonPlayerNameCheck()); + } + public static boolean isValidPlayerNameVanilla(String playerName) { + return playerName.length() <= 16 && playerName.chars().filter(i -> i <= 32 || i >= 127).findAny().isEmpty(); + } + public static boolean isValidPlayerName(String playerName, boolean bypassCheck) { + if (bypassCheck) return playerName.length() <= 16; -+ if (true) return isReasonablePlayerName(playerName); // Leaf - Fix Purpur username validation config -+ // Leaf end - Remove vanilla username check ++ // Leaf end - Configurable vanilla username check return playerName.length() <= 16 && playerName.chars().filter(i -> i <= 32 || i >= 127).findAny().isEmpty(); } -@@ -87,7 +97,12 @@ public class StringUtil { +@@ -87,7 +96,12 @@ public class StringUtil { // Paper start - Username validation public static boolean isReasonablePlayerName(final String name) { - if (true) return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(name).matches(); // Purpur - Configurable valid characters for usernames -+ // Leaf start - Vanilla username check -+ if (true) { -+ if (org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.allowOldPlayersJoin && net.minecraft.server.MinecraftServer.getServer().getPlayerList().playedPlayers.contains(name)) return true; -+ return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(name).matches() && name.length() <= 16; // Purpur - Configurable valid characters for usernames // Leaf - Fix Purpur username validation config - Add length check ++ // Leaf start - Configurable vanilla username check ++ if (org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.allowOldPlayersJoin && net.minecraft.server.MinecraftServer.getServer().getPlayerList().playedPlayers.contains(name)) return true; ++ if (org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.useUsernameRegex) { ++ return org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.usernameRegex.matcher(name).matches() && name.length() <= 16; // Purpur - Configurable valid characters for usernames // Leaf - use our own config + } -+ // Leaf end - Vanilla 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 48517612ff40c83069a52b817a1af9e6ef180db5..5e6441196a697be73f79e4776209791627a5ecc2 100644 +index 48517612ff40c83069a52b817a1af9e6ef180db5..c1937cc39c75c03896f37ea1b516cc5b72e74969 100644 --- a/net/minecraft/world/item/component/ResolvableProfile.java +++ b/net/minecraft/world/item/component/ResolvableProfile.java @@ -39,6 +39,15 @@ public record ResolvableProfile(Optional name, Optional id, Proper ResolvableProfile::new ); -+ // Leaf start - Vanilla username check - Enforce skull validation ++ // Leaf start - Configurable vanilla username check - Enforce skull validation + public ResolvableProfile { + name = sanitizePlayerName(name); + } + private static Optional sanitizePlayerName(Optional name) { + return org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.enforceSkullValidation && !net.minecraft.util.StringUtil.isValidPlayerNameVanilla(name.orElse("NULL_OWNER")) ? Optional.of("INVALID_OWNER") : name; + } -+ // Leaf end - Vanilla username check - Enforce skull validation ++ // Leaf end - Configurable vanilla username check - Enforce skull validation + public ResolvableProfile(Optional name, Optional id, PropertyMap properties) { this(name, id, properties, createGameProfile(id, name, properties)); diff --git a/leaf-server/minecraft-patches/features/0110-Leaves-Replay-Mod-API.patch b/leaf-server/minecraft-patches/features/0110-Leaves-Replay-Mod-API.patch index 3939859e..d7aceee2 100644 --- a/leaf-server/minecraft-patches/features/0110-Leaves-Replay-Mod-API.patch +++ b/leaf-server/minecraft-patches/features/0110-Leaves-Replay-Mod-API.patch @@ -342,7 +342,7 @@ index 1d128d0613d92eb95a753be33a1134c857d0e388..c50cb9bfcd06ec54c0a43d013aa074fb ServerLevel.this.updateSleepingPlayerList(); } diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index 76785d9b11593cc9304e3d16962ddddba2ff8834..e0ae31621ececc54b9e38e524c2b56ffb02949a1 100644 +index 4fbe3d2a7e157f1f06bd0929ee10efd87426554d..fdd2ad453192cfac8dd5310266cbab3bdb5743c6 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -131,6 +131,7 @@ public abstract class PlayerList { @@ -532,7 +532,7 @@ index 76785d9b11593cc9304e3d16962ddddba2ff8834..e0ae31621ececc54b9e38e524c2b56ff this.players.remove(player); + this.realPlayers.remove(player); // Leaves - replay api this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot - if (org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.allowOldPlayersJoin) this.playedPlayers.remove(player.getGameProfile().getName()); // Leaf - Vanilla username check + if (org.dreeam.leaf.config.modules.misc.VanillaUsernameCheck.allowOldPlayersJoin) this.playedPlayers.remove(player.getGameProfile().getName()); // Leaf - Configurable vanilla username check this.removeFromSendAllPlayerInfoBuckets(player); // Gale - Purpur - spread out sending all player info @@ -1011,10 +1158,10 @@ public abstract class PlayerList { } diff --git a/leaf-server/minecraft-patches/features/0229-Async-switch-connection-state.patch b/leaf-server/minecraft-patches/features/0229-Async-switch-connection-state.patch index 9b2fe7af..1999cec2 100644 --- a/leaf-server/minecraft-patches/features/0229-Async-switch-connection-state.patch +++ b/leaf-server/minecraft-patches/features/0229-Async-switch-connection-state.patch @@ -110,10 +110,10 @@ index 7f3379a617932f1e0b0344df75566d2dc6612359..99d9d5048440e535ed6be33df39bf7ae try { PlayerList playerList = this.server.getPlayerList(); diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 970c6f20a3e995dc1b82c3fbabd6ab01a51f22c0..35e8ac5e7a2e4719d407bdc61579d0bc14cc4712 100644 +index a485b708858b6bb87a7ae6ca7f0b2a13dc3b07ed..565870996264ba5bd93b6eaf93b9c9fd8a3ce433 100644 --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -429,11 +429,31 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, +@@ -433,11 +433,31 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); } diff --git a/leaf-server/paper-patches/features/0061-Configurable-vanilla-username-check.patch b/leaf-server/paper-patches/features/0061-Configurable-vanilla-username-check.patch new file mode 100644 index 00000000..67f626dc --- /dev/null +++ b/leaf-server/paper-patches/features/0061-Configurable-vanilla-username-check.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Wed, 12 Oct 2022 14:36:58 -0400 +Subject: [PATCH] Configurable vanilla username check + + +diff --git a/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java +index db43329d57144471d0c9ce0eed92d2f1bd05f120..d65bde85ecae29104c5c34380f164252df9e4515 100644 +--- a/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java ++++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java +@@ -62,6 +62,14 @@ public class CraftPlayerProfile implements PlayerProfile, SharedPlayerProfile { + copyProfileProperties(resolvableProfile.gameProfile(), this.profile); + } + ++ // Leaf start - Configurable vanilla username check ++ public CraftPlayerProfile(UUID id, String name, boolean useCustomValidation) { ++ this.profile = createAuthLibProfile(id, name, useCustomValidation); ++ this.emptyName = name == null; ++ this.emptyUUID = id == null; ++ } ++ // Leaf end - Configurable vanilla username check ++ + @Override + public boolean hasProperty(String property) { + return profile.getProperties().containsKey(property); +@@ -195,6 +203,14 @@ public class CraftPlayerProfile implements PlayerProfile, SharedPlayerProfile { + return clone; + } + ++ // Leaf start - Configurable vanilla username check ++ public CraftPlayerProfile cloneCustom() { ++ CraftPlayerProfile clone = new CraftPlayerProfile(this.getId(), this.getName(), true); ++ clone.setProperties(getProperties()); ++ return clone; ++ } ++ // Leaf end - Configurable vanilla username check ++ + @Override + public boolean isComplete() { + return this.getId() != null && StringUtils.isNotBlank(this.getName()); +@@ -369,15 +385,21 @@ public class CraftPlayerProfile implements PlayerProfile, SharedPlayerProfile { + } + } + +- private static GameProfile createAuthLibProfile(UUID uniqueId, String name) { ++ // Leaf start - Configurable vanilla username check ++ private static GameProfile createAuthLibProfile(UUID uniqueId, String name, boolean useCustomValidation) { + Preconditions.checkArgument(name == null || name.length() <= 16, "Name cannot be longer than 16 characters"); +- Preconditions.checkArgument(name == null || StringUtil.isValidPlayerName(name), "The name of the profile contains invalid characters: %s", name); ++ Preconditions.checkArgument(name == null || (useCustomValidation ? StringUtil.isReasonablePlayerName(name) : StringUtil.isValidPlayerName(name)), "The name of the profile contains invalid characters: %s", name); + return new GameProfile( + uniqueId != null ? uniqueId : Util.NIL_UUID, + name != null ? name : "" + ); + } + ++ private static GameProfile createAuthLibProfile(UUID uniqueId, String name) { ++ return createAuthLibProfile(uniqueId, name, false); ++ } ++ // Leaf end - Configurable vanilla username check ++ + private static ProfileProperty toBukkit(Property property) { + return new ProfileProperty(property.name(), property.value(), property.signature()); + } +@@ -401,6 +423,13 @@ public class CraftPlayerProfile implements PlayerProfile, SharedPlayerProfile { + return asAuthlib(craft.clone()); + } + ++ // Leaf start - Configurable vanilla username check ++ public static GameProfile asAuthlibCopyCustomValidation(PlayerProfile profile) { ++ CraftPlayerProfile craft = ((CraftPlayerProfile) profile); ++ return asAuthlib(craft.cloneCustom()); ++ } ++ // Leaf end - Configurable vanilla username check ++ + public static GameProfile asAuthlib(PlayerProfile profile) { + CraftPlayerProfile craft = ((CraftPlayerProfile) profile); + return craft.getGameProfile(); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/VanillaUsernameCheck.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/VanillaUsernameCheck.java index a3a62f0a..56c00957 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/VanillaUsernameCheck.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/VanillaUsernameCheck.java @@ -2,19 +2,28 @@ package org.dreeam.leaf.config.modules.misc; import org.dreeam.leaf.config.ConfigModules; import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.LeafConfig; import org.dreeam.leaf.config.annotations.Experimental; +import java.util.regex.Pattern; + public class VanillaUsernameCheck extends ConfigModules { public String getBasePath() { return EnumConfigCategory.MISC.getBaseKeyName() + ".vanilla-username-check"; } - @Experimental + @Deprecated public static boolean removeAllCheck = false; public static boolean enforceSkullValidation = true; @Experimental public static boolean allowOldPlayersJoin = false; + public static boolean useUsernameRegex = false; + private static final String defaultRegexString = "^[a-zA-Z0-9_.]*$"; + public static Pattern usernameRegex = Pattern.compile(defaultRegexString); + public static boolean shouldSkipNonPlayerNameCheck() { // helper + return removeAllCheck || useUsernameRegex; + } @Override public void onLoaded() { @@ -38,5 +47,31 @@ public class VanillaUsernameCheck extends ConfigModules { """ 允许老玩家加入修改用户名验证正则后的服务器, 即使他们的用户名不满足修改后的正则.""")); + useUsernameRegex = config.getBoolean(getBasePath() + ".use-username-regex", useUsernameRegex, config.pickStringRegionBased(""" + Use username regex to validate usernames, + allowing only characters specified in the regex.""", + """ + 使用用户名正则来验证用户名, + 只允许正则指定的字符.""")); + String regexString = config.getString(getBasePath() + ".username-regex", defaultRegexString, config.pickStringRegionBased( + """ + Username regex, + specifying the characters allowed in usernames. + Default: %s""".formatted(defaultRegexString), + """ + 用户名正则, + 指定允许在用户名中使用的字符. + 默认: %s""".formatted(defaultRegexString))); + if (!regexString.isBlank()) { + try { + usernameRegex = Pattern.compile(regexString); + } catch (Exception e) { + LeafConfig.LOGGER.error("Invalid username regex {} found, falling back to default.", regexString, e); + } + } + if (useUsernameRegex && removeAllCheck) { + LeafConfig.LOGGER.warn("Found conflicting configuration, remove-all-check and use-username-regex cannot be enabled at same time, ignoring remove-all-check..."); + removeAllCheck = false; + } } }