diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/BanListEntry.java.patch b/paper-server/patches/sources/net/minecraft/server/players/BanListEntry.java.patch similarity index 65% rename from paper-server/patches/unapplied/net/minecraft/server/players/BanListEntry.java.patch rename to paper-server/patches/sources/net/minecraft/server/players/BanListEntry.java.patch index 205eaad746..3e100a7758 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/players/BanListEntry.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/players/BanListEntry.java.patch @@ -1,17 +1,17 @@ --- a/net/minecraft/server/players/BanListEntry.java +++ b/net/minecraft/server/players/BanListEntry.java -@@ -27,7 +27,7 @@ +@@ -26,7 +_,7 @@ } - protected BanListEntry(@Nullable T key, JsonObject json) { -- super(key); -+ super(BanListEntry.checkExpiry(key, json)); // CraftBukkit + protected BanListEntry(@Nullable T user, JsonObject entryData) { +- super(user); ++ super(BanListEntry.checkExpiry(user, entryData)); // CraftBukkit Date date; - -@@ -83,4 +83,22 @@ - json.addProperty("expires", this.expires == null ? "forever" : BanListEntry.DATE_FORMAT.format(this.expires)); - json.addProperty("reason", this.reason); + try { +@@ -80,4 +_,22 @@ + data.addProperty("expires", this.expires == null ? "forever" : DATE_FORMAT.format(this.expires)); + data.addProperty("reason", this.reason); } + + // CraftBukkit start diff --git a/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch b/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch new file mode 100644 index 0000000000..aa0d442fc8 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/players/GameProfileCache.java.patch @@ -0,0 +1,212 @@ +--- a/net/minecraft/server/players/GameProfileCache.java ++++ b/net/minecraft/server/players/GameProfileCache.java +@@ -17,8 +_,6 @@ + import java.io.File; + import java.io.FileNotFoundException; + import java.io.IOException; +-import java.io.Reader; +-import java.io.Writer; + import java.nio.charset.StandardCharsets; + import java.text.DateFormat; + import java.text.ParseException; +@@ -56,6 +_,10 @@ + private final AtomicLong operationCount = new AtomicLong(); + @Nullable + private Executor executor; ++ // Paper start - Fix GameProfileCache concurrency ++ protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock(); ++ protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock(); ++ // Paper end - Fix GameProfileCache concurrency + + public GameProfileCache(GameProfileRepository profileRepository, File file) { + this.profileRepository = profileRepository; +@@ -64,10 +_,12 @@ + } + + private void safeAdd(GameProfileCache.GameProfileInfo profile) { ++ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency + GameProfile profile1 = profile.getProfile(); + profile.setLastAccess(this.getNextOperation()); + this.profilesByName.put(profile1.getName().toLowerCase(Locale.ROOT), profile); + this.profilesByUUID.put(profile1.getId(), profile); ++ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency + } + + private static Optional lookupGameProfile(GameProfileRepository profileRepo, String name) { +@@ -86,6 +_,8 @@ + atomicReference.set(null); + } + }; ++ if (!org.apache.commons.lang3.StringUtils.isBlank(name) // Paper - Don't lookup a profile with a blank name ++ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) // Paper - Add setting for proxy online mode status + profileRepo.findProfilesByNames(new String[]{name}, profileLookupCallback); + GameProfile gameProfile = atomicReference.get(); + return gameProfile != null ? Optional.of(gameProfile) : createUnknownProfile(name); +@@ -101,7 +_,7 @@ + } + + private static boolean usesAuthentication() { +- return usesAuthentication; ++ return io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode(); // Paper - Add setting for proxy online mode status + } + + public void add(GameProfile gameProfile) { +@@ -111,15 +_,29 @@ + Date time = instance.getTime(); + GameProfileCache.GameProfileInfo gameProfileInfo = new GameProfileCache.GameProfileInfo(gameProfile, time); + this.safeAdd(gameProfileInfo); +- this.save(); ++ if(!org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) this.save(true); // Spigot - skip saving if disabled // Paper - Perf: Async GameProfileCache saving + } + + private long getNextOperation() { + return this.operationCount.incrementAndGet(); + } + ++ // Paper start ++ public @Nullable GameProfile getProfileIfCached(String name) { ++ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency ++ GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT)); ++ if (entry == null) { ++ return null; ++ } ++ entry.setLastAccess(this.getNextOperation()); ++ return entry.getProfile(); ++ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency ++ } ++ // Paper end ++ + public Optional get(String name) { + String string = name.toLowerCase(Locale.ROOT); ++ boolean stateLocked = true; try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency + GameProfileCache.GameProfileInfo gameProfileInfo = this.profilesByName.get(string); + boolean flag = false; + if (gameProfileInfo != null && new Date().getTime() >= gameProfileInfo.expirationDate.getTime()) { +@@ -133,19 +_,24 @@ + if (gameProfileInfo != null) { + gameProfileInfo.setLastAccess(this.getNextOperation()); + optional = Optional.of(gameProfileInfo.getProfile()); ++ stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency + } else { +- optional = lookupGameProfile(this.profileRepository, string); ++ stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency ++ try { this.lookupLock.lock(); // Paper - Fix GameProfileCache concurrency ++ optional = lookupGameProfile(this.profileRepository, name); // CraftBukkit - use correct case for offline players ++ } finally { this.lookupLock.unlock(); } // Paper - Fix GameProfileCache concurrency + if (optional.isPresent()) { + this.add(optional.get()); + flag = false; + } + } + +- if (flag) { +- this.save(); ++ if (flag && !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { // Spigot - skip saving if disabled ++ this.save(true); // Paper - Perf: Async GameProfileCache saving + } + + return optional; ++ } finally { if (stateLocked) { this.stateLock.unlock(); } } // Paper - Fix GameProfileCache concurrency + } + + public CompletableFuture> getAsync(String name) { +@@ -157,7 +_,7 @@ + return completableFuture; + } else { + CompletableFuture> completableFuture1 = CompletableFuture.>supplyAsync( +- () -> this.get(name), Util.backgroundExecutor().forName("getProfile") ++ () -> this.get(name), Util.PROFILE_EXECUTOR // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread + ) + .whenCompleteAsync((gameProfile, exception) -> this.requests.remove(name), this.executor); + this.requests.put(name, completableFuture1); +@@ -167,6 +_,7 @@ + } + + public Optional get(UUID uuid) { ++ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency + GameProfileCache.GameProfileInfo gameProfileInfo = this.profilesByUUID.get(uuid); + if (gameProfileInfo == null) { + return Optional.empty(); +@@ -174,6 +_,7 @@ + gameProfileInfo.setLastAccess(this.getNextOperation()); + return Optional.of(gameProfileInfo.getProfile()); + } ++ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency + } + + public void setExecutor(Executor exectutor) { +@@ -193,7 +_,7 @@ + + try { + Object var9; +- try (Reader reader = Files.newReader(this.file, StandardCharsets.UTF_8)) { ++ try (java.io.Reader reader = Files.newReader(this.file, StandardCharsets.UTF_8)) { + JsonArray jsonArray = this.gson.fromJson(reader, JsonArray.class); + if (jsonArray != null) { + DateFormat dateFormat = createDateFormat(); +@@ -206,6 +_,11 @@ + + return (List)var9; + } catch (FileNotFoundException var7) { ++ // Spigot Start ++ } catch (com.google.gson.JsonSyntaxException | NullPointerException ex) { ++ GameProfileCache.LOGGER.warn( "Usercache.json is corrupted or has bad formatting. Deleting it to prevent further issues." ); ++ this.file.delete(); ++ // Spigot End + } catch (JsonParseException | IOException var8) { + LOGGER.warn("Failed to load profile cache {}", this.file, var8); + } +@@ -213,24 +_,45 @@ + return list; + } + +- public void save() { ++ public void save(boolean asyncSave) { // Paper - Perf: Async GameProfileCache saving + JsonArray jsonArray = new JsonArray(); + DateFormat dateFormat = createDateFormat(); +- this.getTopMRUProfiles(1000).forEach(info -> jsonArray.add(writeGameProfile(info, dateFormat))); ++ this.listTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((info) -> jsonArray.add(writeGameProfile(info, dateFormat))); // Spigot // Paper - Fix GameProfileCache concurrency + String string = this.gson.toJson((JsonElement)jsonArray); ++ Runnable save = () -> { // Paper - Perf: Async GameProfileCache saving + +- try (Writer writer = Files.newWriter(this.file, StandardCharsets.UTF_8)) { ++ try (java.io.Writer writer = Files.newWriter(this.file, StandardCharsets.UTF_8)) { + writer.write(string); + } catch (IOException var9) { + } ++ // Paper start - Perf: Async GameProfileCache saving ++ }; ++ if (asyncSave) { ++ io.papermc.paper.util.MCUtil.scheduleAsyncTask(save); ++ } else { ++ save.run(); ++ } ++ // Paper end - Perf: Async GameProfileCache saving + } + + private Stream getTopMRUProfiles(int limit) { +- return ImmutableList.copyOf(this.profilesByUUID.values()) +- .stream() +- .sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()) +- .limit(limit); +- } ++ // Paper start - Fix GameProfileCache concurrency ++ return this.listTopMRUProfiles(limit).stream(); ++ } ++ ++ private List listTopMRUProfiles(int limit) { ++ try { ++ this.stateLock.lock(); ++ return this.profilesByUUID.values() ++ .stream() ++ .sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()) ++ .limit(limit) ++ .toList(); ++ } finally { ++ this.stateLock.unlock(); ++ } ++ } ++ // Paper end - Fix GameProfileCache concurrency + + private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo profileInfo, DateFormat dateFormat) { + JsonObject jsonObject = new JsonObject(); diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/OldUsersConverter.java.patch b/paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch similarity index 53% rename from paper-server/patches/unapplied/net/minecraft/server/players/OldUsersConverter.java.patch rename to paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch index 7f3147ad37..cf078d7683 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/players/OldUsersConverter.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/players/OldUsersConverter.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/server/players/OldUsersConverter.java +++ b/net/minecraft/server/players/OldUsersConverter.java -@@ -21,6 +21,9 @@ +@@ -20,6 +_,9 @@ import java.util.UUID; import javax.annotation.Nullable; import net.minecraft.core.UUIDUtil; @@ -10,62 +10,61 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.util.StringUtil; -@@ -62,7 +65,8 @@ - return new String[i]; - }); +@@ -49,7 +_,8 @@ + private static void lookupPlayers(MinecraftServer server, Collection names, ProfileLookupCallback callback) { + String[] strings = names.stream().filter(name -> !StringUtil.isNullOrEmpty(name)).toArray(String[]::new); - if (server.usesAuthentication()) { + if (server.usesAuthentication() || + (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode())) { // Spigot: bungee = online mode, for now. // Paper - Add setting for proxy online mode status - server.getProfileRepository().findProfilesByNames(astring, callback); + server.getProfileRepository().findProfilesByNames(strings, callback); } else { - String[] astring1 = astring; -@@ -85,7 +89,7 @@ + for (String string : strings) { +@@ -65,7 +_,7 @@ try { - gameprofilebanlist.load(); - } catch (IOException ioexception) { -- OldUsersConverter.LOGGER.warn("Could not load existing file {}", gameprofilebanlist.getFile().getName(), ioexception); -+ OldUsersConverter.LOGGER.warn("Could not load existing file {}", gameprofilebanlist.getFile().getName()); // CraftBukkit - don't print stacktrace + userBanList.load(); + } catch (IOException var6) { +- LOGGER.warn("Could not load existing file {}", userBanList.getFile().getName(), var6); ++ LOGGER.warn("Could not load existing file {}", userBanList.getFile().getName()); // CraftBukkit - don't print stacktrace } } -@@ -143,7 +147,7 @@ +@@ -120,7 +_,7 @@ try { - ipbanlist.load(); - } catch (IOException ioexception) { -- OldUsersConverter.LOGGER.warn("Could not load existing file {}", ipbanlist.getFile().getName(), ioexception); -+ OldUsersConverter.LOGGER.warn("Could not load existing file {}", ipbanlist.getFile().getName()); // CraftBukkit - don't print stacktrace + ipBanList.load(); + } catch (IOException var11) { +- LOGGER.warn("Could not load existing file {}", ipBanList.getFile().getName(), var11); ++ LOGGER.warn("Could not load existing file {}", ipBanList.getFile().getName()); // CraftBukkit - don't print stacktrace } } -@@ -184,7 +188,7 @@ +@@ -156,7 +_,7 @@ try { - oplist.load(); - } catch (IOException ioexception) { -- OldUsersConverter.LOGGER.warn("Could not load existing file {}", oplist.getFile().getName(), ioexception); -+ OldUsersConverter.LOGGER.warn("Could not load existing file {}", oplist.getFile().getName()); // CraftBukkit - don't print stacktrace + serverOpList.load(); + } catch (IOException var6) { +- LOGGER.warn("Could not load existing file {}", serverOpList.getFile().getName(), var6); ++ LOGGER.warn("Could not load existing file {}", serverOpList.getFile().getName()); // CraftBukkit - don't print stacktrace } } -@@ -228,7 +232,7 @@ +@@ -200,7 +_,7 @@ try { - whitelist.load(); - } catch (IOException ioexception) { -- OldUsersConverter.LOGGER.warn("Could not load existing file {}", whitelist.getFile().getName(), ioexception); -+ OldUsersConverter.LOGGER.warn("Could not load existing file {}", whitelist.getFile().getName()); // CraftBukkit - don't print stacktrace + userWhiteList.load(); + } catch (IOException var6) { +- LOGGER.warn("Could not load existing file {}", userWhiteList.getFile().getName(), var6); ++ LOGGER.warn("Could not load existing file {}", userWhiteList.getFile().getName()); // CraftBukkit - don't print stacktrace } } -@@ -346,7 +350,39 @@ - private void movePlayerFile(File playerDataFolder, String fileName, String uuid) { - File file5 = new File(file, fileName + ".dat"); - File file6 = new File(playerDataFolder, uuid + ".dat"); -+ +@@ -313,6 +_,37 @@ + private void movePlayerFile(File file3, String oldFileName, String newFileName) { + File file4 = new File(worldPlayersDirectory, oldFileName + ".dat"); + File file5 = new File(file3, newFileName + ".dat"); + // CraftBukkit start - Use old file name to seed lastKnownName + CompoundTag root = null; + + try { -+ root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap()); ++ root = NbtIo.readCompressed(new java.io.FileInputStream(file4), NbtAccounter.unlimitedHeap()); + } catch (Exception exception) { + // Paper start + io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); @@ -73,16 +72,16 @@ + com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(exception); + // Paper end + } - ++ + if (root != null) { + if (!root.contains("bukkit")) { + root.put("bukkit", new CompoundTag()); + } + CompoundTag data = root.getCompound("bukkit"); -+ data.putString("lastKnownName", fileName); ++ data.putString("lastKnownName", oldFileName); + + try { -+ NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2)); ++ NbtIo.writeCompressed(root, new java.io.FileOutputStream(file1)); + } catch (Exception exception) { + // Paper start + io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); @@ -92,7 +91,6 @@ + } + } + // CraftBukkit end -+ - OldUsersConverter.ensureDirectoryExists(playerDataFolder); - if (!file5.renameTo(file6)) { - throw new OldUsersConverter.ConversionError("Could not convert file for " + fileName); + OldUsersConverter.ensureDirectoryExists(file3); + if (!file4.renameTo(file5)) { + throw new OldUsersConverter.ConversionError("Could not convert file for " + oldFileName); diff --git a/paper-server/patches/sources/net/minecraft/server/players/SleepStatus.java.patch b/paper-server/patches/sources/net/minecraft/server/players/SleepStatus.java.patch new file mode 100644 index 0000000000..feab2a6264 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/players/SleepStatus.java.patch @@ -0,0 +1,41 @@ +--- a/net/minecraft/server/players/SleepStatus.java ++++ b/net/minecraft/server/players/SleepStatus.java +@@ -14,8 +_,11 @@ + } + + public boolean areEnoughDeepSleeping(int requiredSleepPercentage, List sleepingPlayers) { +- int i = (int)sleepingPlayers.stream().filter(Player::isSleepingLongEnough).count(); +- return i >= this.sleepersNeeded(requiredSleepPercentage); ++ // CraftBukkit start ++ int i = (int) sleepingPlayers.stream().filter(player -> player.isSleepingLongEnough() || player.fauxSleeping).count(); ++ boolean anyDeepSleep = sleepingPlayers.stream().anyMatch(Player::isSleepingLongEnough); ++ return anyDeepSleep && i >= this.sleepersNeeded(requiredSleepPercentage); ++ // CraftBukkit end + } + + public int sleepersNeeded(int requiredSleepPercentage) { +@@ -35,16 +_,22 @@ + int i1 = this.sleepingPlayers; + this.activePlayers = 0; + this.sleepingPlayers = 0; ++ boolean anySleep = false; // CraftBukkit + + for (ServerPlayer serverPlayer : players) { + if (!serverPlayer.isSpectator()) { + this.activePlayers++; +- if (serverPlayer.isSleeping()) { ++ if (serverPlayer.isSleeping() || serverPlayer.fauxSleeping) { // CraftBukkit + this.sleepingPlayers++; + } ++ // CraftBukkit start ++ if (serverPlayer.isSleeping()) { ++ anySleep = true; ++ } ++ // CraftBukkit end + } + } + +- return (i1 > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || i1 != this.sleepingPlayers); ++ return anySleep && (i1 > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || i1 != this.sleepingPlayers); // CraftBukkit + } + } diff --git a/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch new file mode 100644 index 0000000000..88ab6f0915 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/players/StoredUserList.java.patch @@ -0,0 +1,81 @@ +--- a/net/minecraft/server/players/StoredUserList.java ++++ b/net/minecraft/server/players/StoredUserList.java +@@ -26,7 +_,7 @@ + private static final Logger LOGGER = LogUtils.getLogger(); + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private final File file; +- private final Map map = Maps.newHashMap(); ++ private final Map map = Maps.newConcurrentMap(); // Paper - Use ConcurrentHashMap in JsonList + + public StoredUserList(File file) { + this.file = file; +@@ -48,8 +_,11 @@ + + @Nullable + public V get(K obj) { +- this.removeExpired(); +- return this.map.get(this.getKeyForUser(obj)); ++ // Paper start - Use ConcurrentHashMap in JsonList ++ return this.map.computeIfPresent(this.getKeyForUser(obj), (k, v) -> { ++ return v.hasExpired() ? null : v; ++ }); ++ // Paper end - Use ConcurrentHashMap in JsonList + } + + public void remove(K user) { +@@ -71,7 +_,7 @@ + } + + public boolean isEmpty() { +- return this.map.size() < 1; ++ return this.map.isEmpty(); // Paper - Use ConcurrentHashMap in JsonList + } + + protected String getKeyForUser(K obj) { +@@ -79,21 +_,12 @@ + } + + protected boolean contains(K entry) { ++ this.removeExpired(); // CraftBukkit - SPIGOT-7589: Consistently remove expired entries to mirror .get(...) + return this.map.containsKey(this.getKeyForUser(entry)); + } + + private void removeExpired() { +- List list = Lists.newArrayList(); +- +- for (V storedUserEntry : this.map.values()) { +- if (storedUserEntry.hasExpired()) { +- list.add(storedUserEntry.getUser()); +- } +- } +- +- for (K object : list) { +- this.map.remove(this.getKeyForUser(object)); +- } ++ this.map.values().removeIf(StoredUserEntry::hasExpired); // Paper - Use ConcurrentHashMap in JsonList + } + + protected abstract StoredUserEntry createEntry(JsonObject entryData); +@@ -103,6 +_,7 @@ + } + + public void save() throws IOException { ++ this.removeExpired(); // Paper - remove expired values before saving + JsonArray jsonArray = new JsonArray(); + this.map.values().stream().map(storedEntry -> Util.make(new JsonObject(), storedEntry::serialize)).forEach(jsonArray::add); + +@@ -127,7 +_,14 @@ + this.map.put(this.getKeyForUser(storedUserEntry.getUser()), (V)storedUserEntry); + } + } ++ // Spigot Start ++ } catch (com.google.gson.JsonParseException | NullPointerException ex) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "Unable to read file " + this.file + ", backing it up to {0}.backup and creating new copy.", ex); ++ File backup = new File(this.file + ".backup"); ++ this.file.renameTo(backup); ++ this.file.delete(); + } ++ // Spigot End + } + } + } diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/UserBanListEntry.java.patch b/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch similarity index 85% rename from paper-server/patches/unapplied/net/minecraft/server/players/UserBanListEntry.java.patch rename to paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch index df038411ce..facd6c1cb4 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/players/UserBanListEntry.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/players/UserBanListEntry.java.patch @@ -1,11 +1,11 @@ --- a/net/minecraft/server/players/UserBanListEntry.java +++ b/net/minecraft/server/players/UserBanListEntry.java -@@ -1,3 +1,4 @@ +@@ -1,3 +_,4 @@ +// mc-dev import package net.minecraft.server.players; import com.google.gson.JsonObject; -@@ -39,20 +40,29 @@ +@@ -37,19 +_,29 @@ @Nullable private static GameProfile createGameProfile(JsonObject json) { @@ -15,13 +15,12 @@ + UUID uuid = null; + String name = null; + if (json.has("uuid")) { - String s = json.get("uuid").getAsString(); + String asString = json.get("uuid").getAsString(); - UUID uuid; -- try { - uuid = UUID.fromString(s); - } catch (Throwable throwable) { + uuid = UUID.fromString(asString); + } catch (Throwable var4) { - return null; } diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/UserWhiteList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/UserWhiteList.java.patch similarity index 88% rename from paper-server/patches/unapplied/net/minecraft/server/players/UserWhiteList.java.patch rename to paper-server/patches/sources/net/minecraft/server/players/UserWhiteList.java.patch index 4cef2c2bc4..9f81bd284e 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/players/UserWhiteList.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/players/UserWhiteList.java.patch @@ -1,8 +1,8 @@ --- a/net/minecraft/server/players/UserWhiteList.java +++ b/net/minecraft/server/players/UserWhiteList.java -@@ -28,4 +28,23 @@ - protected String getKeyForUser(GameProfile gameProfile) { - return gameProfile.getId().toString(); +@@ -28,4 +_,23 @@ + protected String getKeyForUser(GameProfile obj) { + return obj.getId().toString(); } + // Paper start - Add whitelist events + @Override diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/GameProfileCache.java.patch b/paper-server/patches/unapplied/net/minecraft/server/players/GameProfileCache.java.patch deleted file mode 100644 index 8b31e2142b..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/server/players/GameProfileCache.java.patch +++ /dev/null @@ -1,217 +0,0 @@ ---- a/net/minecraft/server/players/GameProfileCache.java -+++ b/net/minecraft/server/players/GameProfileCache.java -@@ -60,6 +60,11 @@ - @Nullable - private Executor executor; - -+ // Paper start - Fix GameProfileCache concurrency -+ protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock(); -+ protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock(); -+ // Paper end - Fix GameProfileCache concurrency -+ - public GameProfileCache(GameProfileRepository profileRepository, File cacheFile) { - this.profileRepository = profileRepository; - this.file = cacheFile; -@@ -67,11 +72,13 @@ - } - - private void safeAdd(GameProfileCache.GameProfileInfo entry) { -+ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency - GameProfile gameprofile = entry.getProfile(); - - entry.setLastAccess(this.getNextOperation()); - this.profilesByName.put(gameprofile.getName().toLowerCase(Locale.ROOT), entry); - this.profilesByUUID.put(gameprofile.getId(), entry); -+ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency - } - - private static Optional lookupGameProfile(GameProfileRepository repository, String name) { -@@ -85,10 +92,12 @@ - } - - public void onProfileLookupFailed(String s1, Exception exception) { -- atomicreference.set((Object) null); -+ atomicreference.set(null); // CraftBukkit - decompile error - } - }; - -+ if (!org.apache.commons.lang3.StringUtils.isBlank(name) // Paper - Don't lookup a profile with a blank name -+ && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()) // Paper - Add setting for proxy online mode status - repository.findProfilesByNames(new String[]{name}, profilelookupcallback); - GameProfile gameprofile = (GameProfile) atomicreference.get(); - -@@ -105,7 +114,7 @@ - } - - private static boolean usesAuthentication() { -- return GameProfileCache.usesAuthentication; -+ return io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode(); // Paper - Add setting for proxy online mode status - } - - public void add(GameProfile profile) { -@@ -117,15 +126,29 @@ - GameProfileCache.GameProfileInfo usercache_usercacheentry = new GameProfileCache.GameProfileInfo(profile, date); - - this.safeAdd(usercache_usercacheentry); -- this.save(); -+ if( !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly ) this.save(true); // Spigot - skip saving if disabled // Paper - Perf: Async GameProfileCache saving - } - - private long getNextOperation() { - return this.operationCount.incrementAndGet(); - } - -+ // Paper start -+ public @Nullable GameProfile getProfileIfCached(String name) { -+ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency -+ GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT)); -+ if (entry == null) { -+ return null; -+ } -+ entry.setLastAccess(this.getNextOperation()); -+ return entry.getProfile(); -+ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency -+ } -+ // Paper end -+ - public Optional get(String name) { - String s1 = name.toLowerCase(Locale.ROOT); -+ boolean stateLocked = true; try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency - GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1); - boolean flag = false; - -@@ -141,19 +164,24 @@ - if (usercache_usercacheentry != null) { - usercache_usercacheentry.setLastAccess(this.getNextOperation()); - optional = Optional.of(usercache_usercacheentry.getProfile()); -+ stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency - } else { -- optional = GameProfileCache.lookupGameProfile(this.profileRepository, s1); -+ stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency -+ try { this.lookupLock.lock(); // Paper - Fix GameProfileCache concurrency -+ optional = GameProfileCache.lookupGameProfile(this.profileRepository, name); // CraftBukkit - use correct case for offline players -+ } finally { this.lookupLock.unlock(); } // Paper - Fix GameProfileCache concurrency - if (optional.isPresent()) { - this.add((GameProfile) optional.get()); - flag = false; - } - } - -- if (flag) { -- this.save(); -+ if (flag && !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) { // Spigot - skip saving if disabled -+ this.save(true); // Paper - Perf: Async GameProfileCache saving - } - - return optional; -+ } finally { if (stateLocked) { this.stateLock.unlock(); } } // Paper - Fix GameProfileCache concurrency - } - - public CompletableFuture> getAsync(String username) { -@@ -167,7 +195,7 @@ - } else { - CompletableFuture> completablefuture1 = CompletableFuture.supplyAsync(() -> { - return this.get(username); -- }, Util.backgroundExecutor().forName("getProfile")).whenCompleteAsync((optional, throwable) -> { -+ }, Util.PROFILE_EXECUTOR).whenCompleteAsync((optional, throwable) -> { // Paper - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread - this.requests.remove(username); - }, this.executor); - -@@ -178,6 +206,7 @@ - } - - public Optional get(UUID uuid) { -+ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency - GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByUUID.get(uuid); - - if (usercache_usercacheentry == null) { -@@ -186,6 +215,7 @@ - usercache_usercacheentry.setLastAccess(this.getNextOperation()); - return Optional.of(usercache_usercacheentry.getProfile()); - } -+ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency - } - - public void setExecutor(Executor executor) { -@@ -208,7 +238,7 @@ - - label54: - { -- ArrayList arraylist; -+ List arraylist; // CraftBukkit - decompile error - - try { - JsonArray jsonarray = (JsonArray) this.gson.fromJson(bufferedreader, JsonArray.class); -@@ -217,7 +247,7 @@ - DateFormat dateformat = GameProfileCache.createDateFormat(); - - jsonarray.forEach((jsonelement) -> { -- Optional optional = GameProfileCache.readGameProfile(jsonelement, dateformat); -+ Optional optional = GameProfileCache.readGameProfile(jsonelement, dateformat); // CraftBukkit - decompile error - - Objects.requireNonNull(list); - optional.ifPresent(list::add); -@@ -250,6 +280,11 @@ - } - } catch (FileNotFoundException filenotfoundexception) { - ; -+ // Spigot Start -+ } catch (com.google.gson.JsonSyntaxException | NullPointerException ex) { -+ GameProfileCache.LOGGER.warn( "Usercache.json is corrupted or has bad formatting. Deleting it to prevent further issues." ); -+ this.file.delete(); -+ // Spigot End - } catch (JsonParseException | IOException ioexception) { - GameProfileCache.LOGGER.warn("Failed to load profile cache {}", this.file, ioexception); - } -@@ -257,14 +292,15 @@ - return list; - } - -- public void save() { -+ public void save(boolean asyncSave) { // Paper - Perf: Async GameProfileCache saving - JsonArray jsonarray = new JsonArray(); - DateFormat dateformat = GameProfileCache.createDateFormat(); - -- this.getTopMRUProfiles(1000).forEach((usercache_usercacheentry) -> { -+ this.listTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((usercache_usercacheentry) -> { // Spigot // Paper - Fix GameProfileCache concurrency - jsonarray.add(GameProfileCache.writeGameProfile(usercache_usercacheentry, dateformat)); - }); - String s = this.gson.toJson(jsonarray); -+ Runnable save = () -> { // Paper - Perf: Async GameProfileCache saving - - try { - BufferedWriter bufferedwriter = Files.newWriter(this.file, StandardCharsets.UTF_8); -@@ -289,13 +325,32 @@ - } catch (IOException ioexception) { - ; - } -+ // Paper start - Perf: Async GameProfileCache saving -+ }; -+ if (asyncSave) { -+ io.papermc.paper.util.MCUtil.scheduleAsyncTask(save); -+ } else { -+ save.run(); -+ } -+ // Paper end - Perf: Async GameProfileCache saving - - } - - private Stream getTopMRUProfiles(int limit) { -- return ImmutableList.copyOf(this.profilesByUUID.values()).stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit((long) limit); -+ // Paper start - Fix GameProfileCache concurrency -+ return this.listTopMRUProfiles(limit).stream(); - } - -+ private List listTopMRUProfiles(int limit) { -+ try { -+ this.stateLock.lock(); -+ return this.profilesByUUID.values().stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit(limit).toList(); -+ } finally { -+ this.stateLock.unlock(); -+ } -+ } -+ // Paper end - Fix GameProfileCache concurrency -+ - private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo entry, DateFormat dateFormat) { - JsonObject jsonobject = new JsonObject(); - diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/SleepStatus.java.patch b/paper-server/patches/unapplied/net/minecraft/server/players/SleepStatus.java.patch deleted file mode 100644 index 568224fe25..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/server/players/SleepStatus.java.patch +++ /dev/null @@ -1,44 +0,0 @@ ---- a/net/minecraft/server/players/SleepStatus.java -+++ b/net/minecraft/server/players/SleepStatus.java -@@ -18,9 +18,12 @@ - } - - public boolean areEnoughDeepSleeping(int percentage, List players) { -- int j = (int) players.stream().filter(Player::isSleepingLongEnough).count(); -+ // CraftBukkit start -+ int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count(); -+ boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough); - -- return j >= this.sleepersNeeded(percentage); -+ return anyDeepSleep && j >= this.sleepersNeeded(percentage); -+ // CraftBukkit end - } - - public int sleepersNeeded(int percentage) { -@@ -42,18 +45,24 @@ - this.activePlayers = 0; - this.sleepingPlayers = 0; - Iterator iterator = players.iterator(); -+ boolean anySleep = false; // CraftBukkit - - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); - - if (!entityplayer.isSpectator()) { - ++this.activePlayers; -- if (entityplayer.isSleeping()) { -+ if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit - ++this.sleepingPlayers; - } -+ // CraftBukkit start -+ if (entityplayer.isSleeping()) { -+ anySleep = true; -+ } -+ // CraftBukkit end - } - } - -- return (j > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || j != this.sleepingPlayers); -+ return anySleep && (j > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || j != this.sleepingPlayers); // CraftBukkit - } - } diff --git a/paper-server/patches/unapplied/net/minecraft/server/players/StoredUserList.java.patch b/paper-server/patches/unapplied/net/minecraft/server/players/StoredUserList.java.patch deleted file mode 100644 index bea194db95..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/server/players/StoredUserList.java.patch +++ /dev/null @@ -1,96 +0,0 @@ ---- a/net/minecraft/server/players/StoredUserList.java -+++ b/net/minecraft/server/players/StoredUserList.java -@@ -30,7 +30,7 @@ - private static final Logger LOGGER = LogUtils.getLogger(); - private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create(); - private final File file; -- private final Map map = Maps.newHashMap(); -+ private final Map map = Maps.newConcurrentMap(); // Paper - Use ConcurrentHashMap in JsonList - - public StoredUserList(File file) { - this.file = file; -@@ -53,8 +53,11 @@ - - @Nullable - public V get(K key) { -- this.removeExpired(); -- return (StoredUserEntry) this.map.get(this.getKeyForUser(key)); -+ // Paper start - Use ConcurrentHashMap in JsonList -+ return (V) this.map.computeIfPresent(this.getKeyForUser(key), (k, v) -> { -+ return v.hasExpired() ? null : v; -+ }); -+ // Paper end - Use ConcurrentHashMap in JsonList - } - - public void remove(K key) { -@@ -77,7 +80,7 @@ - } - - public boolean isEmpty() { -- return this.map.size() < 1; -+ return this.map.isEmpty(); // Paper - Use ConcurrentHashMap in JsonList - } - - protected String getKeyForUser(K profile) { -@@ -85,29 +88,12 @@ - } - - protected boolean contains(K k0) { -+ this.removeExpired(); // CraftBukkit - SPIGOT-7589: Consistently remove expired entries to mirror .get(...) - return this.map.containsKey(this.getKeyForUser(k0)); - } - - private void removeExpired() { -- List list = Lists.newArrayList(); -- Iterator iterator = this.map.values().iterator(); -- -- while (iterator.hasNext()) { -- V v0 = (StoredUserEntry) iterator.next(); -- -- if (v0.hasExpired()) { -- list.add(v0.getUser()); -- } -- } -- -- iterator = list.iterator(); -- -- while (iterator.hasNext()) { -- K k0 = iterator.next(); -- -- this.map.remove(this.getKeyForUser(k0)); -- } -- -+ this.map.values().removeIf(StoredUserEntry::hasExpired); // Paper - Use ConcurrentHashMap in JsonList - } - - protected abstract StoredUserEntry createEntry(JsonObject json); -@@ -117,8 +103,9 @@ - } - - public void save() throws IOException { -+ this.removeExpired(); // Paper - remove expired values before saving - JsonArray jsonarray = new JsonArray(); -- Stream stream = this.map.values().stream().map((jsonlistentry) -> { -+ Stream stream = this.map.values().stream().map((jsonlistentry) -> { // CraftBukkit - decompile error - JsonObject jsonobject = new JsonObject(); - - Objects.requireNonNull(jsonlistentry); -@@ -171,9 +158,17 @@ - StoredUserEntry jsonlistentry = this.createEntry(jsonobject); - - if (jsonlistentry.getUser() != null) { -- this.map.put(this.getKeyForUser(jsonlistentry.getUser()), jsonlistentry); -+ this.map.put(this.getKeyForUser(jsonlistentry.getUser()), (V) jsonlistentry); // CraftBukkit - decompile error - } - } -+ // Spigot Start -+ } catch ( com.google.gson.JsonParseException | NullPointerException ex ) -+ { -+ org.bukkit.Bukkit.getLogger().log( java.util.logging.Level.WARNING, "Unable to read file " + this.file + ", backing it up to {0}.backup and creating new copy.", ex ); -+ File backup = new File( this.file + ".backup" ); -+ this.file.renameTo( backup ); -+ this.file.delete(); -+ // Spigot End - } catch (Throwable throwable) { - if (bufferedreader != null) { - try {