This commit is contained in:
Nassim Jahnke
2021-06-13 11:41:07 +02:00
parent bda639725b
commit 43227a418f
19 changed files with 399 additions and 800 deletions

View File

@@ -1,271 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Fri, 15 Feb 2019 01:08:19 -0500
Subject: [PATCH] Allow Saving of Oversized Chunks
The Minecraft World Region File format has a hard cap of 1MB per chunk.
This is due to the fact that the header of the file format only allocates
a single byte for sector count, meaning a maximum of 256 sectors, at 4k per sector.
This limit can be reached fairly easily with books, resulting in the chunk being unable
to save to the world. Worse off, is that nothing printed when this occured, and silently
performed a chunk rollback on next load.
This leads to security risk with duplication and is being actively exploited.
This patch catches the too large scenario, falls back and moves any large Entity
or Tile Entity into a new compound, and this compound is saved into a different file.
On Chunk Load, we check for oversized status, and if so, we load the extra file and
merge the Entities and Tile Entities from the oversized chunk back into the level to
then be loaded as normal.
Once a chunk is returned back to normal size, the oversized flag will clear, and no
extra data file will exist.
This fix maintains compatability with all existing Anvil Region Format tools as it
does not alter the save format. They will just not know about the extra entities.
This fix also maintains compatability if someone switches server jars to one without
this fix, as the data will remain in the oversized file. Once the server returns
to a jar with this fix, the data will be restored.
diff --git a/src/main/java/net/minecraft/nbt/NbtIo.java b/src/main/java/net/minecraft/nbt/NbtIo.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/nbt/NbtIo.java
+++ b/src/main/java/net/minecraft/nbt/NbtIo.java
@@ -0,0 +0,0 @@ public class NbtIo {
}
+ public static CompoundTag readNBT(DataInput datainput) throws IOException { return read(datainput); } // Paper - OBFHELPER
public static CompoundTag read(DataInput input) throws IOException {
return read(input, NbtAccounter.UNLIMITED);
}
@@ -0,0 +0,0 @@ public class NbtIo {
}
}
+ public static void writeNBT(CompoundTag nbttagcompound, DataOutput dataoutput) throws IOException { write(nbttagcompound, dataoutput); } // Paper - OBFHELPER
public static void write(CompoundTag tag, DataOutput output) throws IOException {
writeUnnamedTag((Tag) tag, output);
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -0,0 +0,0 @@ import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
+import java.util.zip.InflaterInputStream; // Paper
+
import javax.annotation.Nullable;
import net.minecraft.Util;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.ChunkPos;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
private final IntBuffer timestamps;
@VisibleForTesting
protected final RegionBitmap usedSectors;
+ public final File file; // Paper
public RegionFile(File file, File directory, boolean dsync) throws IOException {
this(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync);
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException {
this.header = ByteBuffer.allocateDirect(8192);
+ this.file = file.toFile(); // Paper
+ initOversizedState(); // Paper
this.usedSectors = new RegionBitmap();
this.version = outputChunkStreamVersion;
if (!Files.isDirectory(directory, new LinkOption[0])) {
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
void run() throws IOException;
}
+ // Paper start
+ private final byte[] oversized = new byte[1024];
+ private int oversizedCount = 0;
+
+ private synchronized void initOversizedState() throws IOException {
+ File metaFile = getOversizedMetaFile();
+ if (metaFile.exists()) {
+ final byte[] read = java.nio.file.Files.readAllBytes(metaFile.toPath());
+ System.arraycopy(read, 0, oversized, 0, oversized.length);
+ for (byte temp : oversized) {
+ oversizedCount += temp;
+ }
+ }
+ }
+
+ private static int getChunkIndex(int x, int z) {
+ return (x & 31) + (z & 31) * 32;
+ }
+ synchronized boolean isOversized(int x, int z) {
+ return this.oversized[getChunkIndex(x, z)] == 1;
+ }
+ synchronized void setOversized(int x, int z, boolean oversized) throws IOException {
+ final int offset = getChunkIndex(x, z);
+ boolean previous = this.oversized[offset] == 1;
+ this.oversized[offset] = (byte) (oversized ? 1 : 0);
+ if (!previous && oversized) {
+ oversizedCount++;
+ } else if (!oversized && previous) {
+ oversizedCount--;
+ }
+ if (previous && !oversized) {
+ File oversizedFile = getOversizedFile(x, z);
+ if (oversizedFile.exists()) {
+ oversizedFile.delete();
+ }
+ }
+ if (oversizedCount > 0) {
+ if (previous != oversized) {
+ writeOversizedMeta();
+ }
+ } else if (previous) {
+ File oversizedMetaFile = getOversizedMetaFile();
+ if (oversizedMetaFile.exists()) {
+ oversizedMetaFile.delete();
+ }
+ }
+ }
+
+ private void writeOversizedMeta() throws IOException {
+ java.nio.file.Files.write(getOversizedMetaFile().toPath(), oversized);
+ }
+
+ private File getOversizedMetaFile() {
+ return new File(this.file.getParentFile(), this.file.getName().replaceAll("\\.mca$", "") + ".oversized.nbt");
+ }
+
+ private File getOversizedFile(int x, int z) {
+ return new File(this.file.getParentFile(), this.file.getName().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt");
+ }
+
+ synchronized CompoundTag getOversizedData(int x, int z) throws IOException {
+ File file = getOversizedFile(x, z);
+ try (DataInputStream out = new DataInputStream(new BufferedInputStream(new InflaterInputStream(new java.io.FileInputStream(file))))) {
+ return NbtIo.readNBT((java.io.DataInput) out);
+ }
+
+ }
+ // Paper end
class ChunkBuffer extends ByteArrayOutputStream {
private final ChunkPos pos;
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -0,0 +0,0 @@ import java.io.File;
import java.io.IOException;
import javax.annotation.Nullable;
import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtIo;
+import net.minecraft.nbt.Tag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ExceptionCollector;
import net.minecraft.world.level.ChunkPos;
@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
}
}
+ // Paper start
+ private static void printOversizedLog(String msg, File file, int x, int z) {
+ org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed.");
+ }
+
+ private static final int DEFAULT_SIZE_THRESHOLD = 1024 * 8;
+ private static final int OVERZEALOUS_TOTAL_THRESHOLD = 1024 * 64;
+ private static final int OVERZEALOUS_THRESHOLD = 1024;
+ private static int SIZE_THRESHOLD = DEFAULT_SIZE_THRESHOLD;
+ private static void resetFilterThresholds() {
+ SIZE_THRESHOLD = Math.max(1024 * 4, Integer.getInteger("Paper.FilterThreshhold", DEFAULT_SIZE_THRESHOLD));
+ }
+ static {
+ resetFilterThresholds();
+ }
+
+ static boolean isOverzealous() {
+ return SIZE_THRESHOLD == OVERZEALOUS_THRESHOLD;
+ }
+
+
+ private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException {
+ synchronized (regionfile) {
+ try (DataInputStream datainputstream = regionfile.getReadStream(chunkCoordinate)) {
+ CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z);
+ CompoundTag chunk = NbtIo.readNBT((DataInput) datainputstream);
+ if (oversizedData == null) {
+ return chunk;
+ }
+ CompoundTag oversizedLevel = oversizedData.getCompound("Level");
+ CompoundTag level = chunk.getCompound("Level");
+
+ mergeChunkList(level, oversizedLevel, "Entities");
+ mergeChunkList(level, oversizedLevel, "TileEntities");
+
+ chunk.put("Level", level);
+
+ return chunk;
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ throw throwable;
+ }
+ }
+ }
+
+ private static void mergeChunkList(CompoundTag level, CompoundTag oversizedLevel, String key) {
+ ListTag levelList = level.getList(key, 10);
+ ListTag oversizedList = oversizedLevel.getList(key, 10);
+
+ if (!oversizedList.isEmpty()) {
+ levelList.addAll(oversizedList);
+ level.put(key, levelList);
+ }
+ }
+
+ private static int getNBTSize(Tag nbtBase) {
+ DataOutputStream test = new DataOutputStream(new org.apache.commons.io.output.NullOutputStream());
+ try {
+ nbtBase.write(test);
+ return test.size();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return 0;
+ }
+ }
+
+ // Paper End
+
@Nullable
public CompoundTag read(ChunkPos pos) throws IOException {
// CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
}
// CraftBukkit end
DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
+ // Paper start
+ if (regionfile.isOversized(pos.x, pos.z)) {
+ printOversizedLog("Loading Oversized Chunk!", regionfile.file, pos.x, pos.z);
+ return readOversizedChunk(regionfile, pos);
+ }
+ // Paper end
Throwable throwable = null;
CompoundTag nbttagcompound;
@@ -0,0 +0,0 @@ public final class RegionFileStorage implements AutoCloseable {
try {
NbtIo.write(tag, (DataOutput) dataoutputstream);
+ regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
} catch (Throwable throwable1) {
throwable = throwable1;
throw throwable1;

View File

@@ -1,18 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mark Vainomaa <mikroskeem@mikroskeem.eu>
Date: Wed, 13 Mar 2019 20:08:09 +0200
Subject: [PATCH] Call WhitelistToggleEvent when whitelist is toggled
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -0,0 +0,0 @@ public abstract class PlayerList {
}
public void setUsingWhiteList(boolean whitelistEnabled) {
+ new com.destroystokyo.paper.event.server.WhitelistToggleEvent(whitelistEnabled).callEvent();
this.doWhiteList = whitelistEnabled;
}

View File

@@ -1,121 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 24 Mar 2019 00:24:52 -0400
Subject: [PATCH] Entity#getEntitySpawnReason
Allows you to return the SpawnReason for why an Entity Spawned
Pre existing entities will return NATURAL if it was a non
persistenting Living Entity, SPAWNER for spawners,
or DEFAULT since data was not stored.
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -0,0 +0,0 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
// CraftBukkit start
private boolean addEntity0(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) {
org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot
+ if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper
// Paper start
if (entity.valid) {
MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable());
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -0,0 +0,0 @@ public abstract class PlayerList {
// CraftBukkit start
ServerLevel finalWorldServer = worldserver1;
Entity entity = EntityType.loadEntityRecursive(nbttagcompound1.getCompound("Entity"), finalWorldServer, (entity1) -> {
- return !finalWorldServer.addWithUUID(entity1) ? null : entity1;
+ return !finalWorldServer.addEntitySerialized(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; // Paper
// CraftBukkit end
});
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -0,0 +0,0 @@ import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.Nameable;
import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.animal.AbstractFish;
+import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat;
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
}
};
public List<Entity> entitySlice = null;
+ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason;
// Paper end
public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
tag.setUUID("Paper.OriginWorld", origin.getWorld().getUID());
tag.put("Paper.Origin", this.createList(origin.getX(), origin.getY(), origin.getZ()));
}
+ if (spawnReason != null) {
+ tag.putString("Paper.SpawnReason", spawnReason.name());
+ }
// Save entity's from mob spawner status
if (spawnedViaMobSpawner) {
tag.putBoolean("Paper.FromMobSpawner", true);
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
}
spawnedViaMobSpawner = tag.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status
+ if (tag.contains("Paper.SpawnReason")) {
+ String spawnReasonName = tag.getString("Paper.SpawnReason");
+ try {
+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.valueOf(spawnReasonName);
+ } catch (Exception ignored) {
+ LogManager.getLogger().error("Unknown SpawnReason " + spawnReasonName + " for " + this);
+ }
+ }
+ if (spawnReason == null) {
+ if (spawnedViaMobSpawner) {
+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER;
+ } else if (this instanceof Mob && (this instanceof Animal || this instanceof AbstractFish) && !((Mob) this).removeWhenFarAway(0.0)) {
+ if (!tag.getBoolean("PersistenceRequired")) {
+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL;
+ }
+ }
+ }
+ if (spawnReason == null) {
+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT;
+ }
// Paper end
} catch (Throwable throwable) {
diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java
+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java
@@ -0,0 +0,0 @@ public abstract class BaseSpawner {
// Spigot End
}
entity.spawnedViaMobSpawner = true; // Paper
+ entity.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER; // Paper
flag = true; // Paper
// Spigot Start
if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, blockposition).isCancelled()) {
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -0,0 +0,0 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
public boolean fromMobSpawner() {
return getHandle().spawnedViaMobSpawner;
}
+
+ @Override
+ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason getEntitySpawnReason() {
+ return getHandle().spawnReason;
+ }
// Paper end
}

View File

@@ -1,265 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mark Vainomaa <mikroskeem@mikroskeem.eu>
Date: Sun, 17 Mar 2019 21:46:56 +0200
Subject: [PATCH] Fire event on GS4 query
diff --git a/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java b/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java
+++ b/src/main/java/net/minecraft/server/rcon/NetworkDataOutputStream.java
@@ -0,0 +0,0 @@ public class NetworkDataOutputStream {
this.dataOutputStream.write(abyte, 0, abyte.length);
}
+ public void writeString(String string) throws IOException { this.writeString(string); } // Paper - OBFHELPER
public void writeString(String s) throws IOException {
this.dataOutputStream.writeBytes(s);
this.dataOutputStream.write(0);
}
+ // Paper start - unchecked exception variant to use in Stream API
+ public void writeStringUnchecked(String string) {
+ try {
+ writeString(string);
+ } catch (IOException e) {
+ com.destroystokyo.paper.util.SneakyThrow.sneaky(e);
+ }
+ }
+ // Paper end
+ public void writeInt(int i) throws IOException { this.write(i); } // Paper - OBFHELPER
public void write(int i) throws IOException {
this.dataOutputStream.write(i);
}
+ public void writeShort(short i) throws IOException { this.writeShort(i); } // Paper - OBFHELPER
public void writeShort(short short0) throws IOException {
this.dataOutputStream.writeShort(Short.reverseBytes(short0));
}
diff --git a/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java b/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java
+++ b/src/main/java/net/minecraft/server/rcon/thread/QueryThreadGs4.java
@@ -0,0 +0,0 @@ import java.util.Random;
import javax.annotation.Nullable;
import net.minecraft.Util;
import net.minecraft.server.ServerInterface;
+import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.rcon.NetworkDataOutputStream;
import net.minecraft.server.rcon.PktUtils;
import org.apache.logging.log4j.LogManager;
@@ -0,0 +0,0 @@ public class QueryThreadGs4 extends GenericThread {
private static final Logger LOGGER = LogManager.getLogger();
private long lastChallengeCheck;
private final int port;
- private final int serverPort;
- private final int maxPlayers;
- private final String serverName;
- private final String worldName;
+ private final int serverPort; private final int getServerPort() { return this.serverPort; } // Paper - OBFHELPER
+ private final int maxPlayers; private final int getMaxPlayers() { return this.maxPlayers; } // Paper - OBFHELPER
+ private final String serverName; private final String getMotd() { return this.serverName; } // Paper - OBFHELPER
+ private final String worldName; private final String getWorldName() { return this.worldName; } // Paper - OBFHELPER
private DatagramSocket socket;
private final byte[] buffer = new byte[1460];
- private String hostIp;
+ private String hostIp; public final String getServerHost() { return this.hostIp; } // Paper - OBFHELPER
private String serverIp;
private final Map<SocketAddress, QueryThreadGs4.RequestChallenge> validChallenges;
- private final NetworkDataOutputStream rulesResponse;
+ private final NetworkDataOutputStream rulesResponse; private final NetworkDataOutputStream getCachedFullResponse() { return this.rulesResponse; } // Paper - OBFHELPER
private long lastRulesResponse;
- private final ServerInterface serverInterface;
+ private final ServerInterface serverInterface; private final ServerInterface getServer() { return this.serverInterface; } // Paper - OBFHELPER
private QueryThreadGs4(ServerInterface server, int queryPort) {
super("Query Listener");
@@ -0,0 +0,0 @@ public class QueryThreadGs4 extends GenericThread {
remotestatusreply.write((int) 0);
remotestatusreply.writeBytes(this.getIdentBytes(packet.getSocketAddress()));
- remotestatusreply.writeString(this.serverName);
+ /* Paper start - GS4 Query event
+ remotestatusreply.a(this.i);
+ remotestatusreply.a("SMP");
+ remotestatusreply.a(this.j);
+ remotestatusreply.a(Integer.toString(this.r.getPlayerCount()));
+ remotestatusreply.a(Integer.toString(this.h));
+ remotestatusreply.a((short) this.g);
+ remotestatusreply.a(this.m);
+ */
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType =
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.BASIC;
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder()
+ .motd(this.getMotd())
+ .map(this.getWorldName())
+ .currentPlayers(this.getServer().getPlayerCount())
+ .maxPlayers(this.getMaxPlayers())
+ .port(this.getServerPort())
+ .hostname(this.getServerHost())
+ .gameVersion(this.getServer().getServerVersion())
+ .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion())
+ .build();
+ com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent =
+ new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse);
+ queryEvent.callEvent();
+ queryResponse = queryEvent.getResponse();
+ remotestatusreply.writeString(queryResponse.getMotd());
remotestatusreply.writeString("SMP");
- remotestatusreply.writeString(this.worldName);
- remotestatusreply.writeString(Integer.toString(this.serverInterface.getPlayerCount()));
- remotestatusreply.writeString(Integer.toString(this.maxPlayers));
- remotestatusreply.writeShort((short) this.serverPort);
- remotestatusreply.writeString(this.hostIp);
+ remotestatusreply.writeString(queryResponse.getMap());
+ remotestatusreply.writeString(Integer.toString(queryResponse.getCurrentPlayers()));
+ remotestatusreply.writeString(Integer.toString(queryResponse.getMaxPlayers()));
+ remotestatusreply.writeShort((short) queryResponse.getPort());
+ remotestatusreply.writeString(queryResponse.getHostname());
+ // Paper end
this.sendTo(remotestatusreply.toByteArray(), packet);
QueryThreadGs4.LOGGER.debug("Status [{}]", socketaddress);
}
@@ -0,0 +0,0 @@ public class QueryThreadGs4 extends GenericThread {
this.rulesResponse.writeString("splitnum");
this.rulesResponse.write((int) 128);
this.rulesResponse.write((int) 0);
- this.rulesResponse.writeString("hostname");
- this.rulesResponse.writeString(this.serverName);
- this.rulesResponse.writeString("gametype");
- this.rulesResponse.writeString("SMP");
- this.rulesResponse.writeString("game_id");
- this.rulesResponse.writeString("MINECRAFT");
- this.rulesResponse.writeString("version");
- this.rulesResponse.writeString(this.serverInterface.getServerVersion());
- this.rulesResponse.writeString("plugins");
- this.rulesResponse.writeString(this.serverInterface.getPluginNames());
- this.rulesResponse.writeString("map");
- this.rulesResponse.writeString(this.worldName);
- this.rulesResponse.writeString("numplayers");
- this.rulesResponse.writeString("" + this.serverInterface.getPlayerCount());
- this.rulesResponse.writeString("maxplayers");
- this.rulesResponse.writeString("" + this.maxPlayers);
- this.rulesResponse.writeString("hostport");
- this.rulesResponse.writeString("" + this.serverPort);
- this.rulesResponse.writeString("hostip");
- this.rulesResponse.writeString(this.hostIp);
- this.rulesResponse.write((int) 0);
- this.rulesResponse.write((int) 1);
- this.rulesResponse.writeString("player_");
- this.rulesResponse.write((int) 0);
- String[] astring = this.serverInterface.getPlayerNames();
+ /* Paper start - GS4 Query event
+ this.p.a("hostname");
+ this.p.a(this.i);
+ this.p.a("gametype");
+ this.p.a("SMP");
+ this.p.a("game_id");
+ this.p.a("MINECRAFT");
+ this.p.a("version");
+ this.p.a(this.r.getVersion());
+ this.p.a("plugins");
+ this.p.a(this.r.getPlugins());
+ this.p.a("map");
+ this.p.a(this.j);
+ this.p.a("numplayers");
+ this.p.a("" + this.r.getPlayerCount());
+ this.p.a("maxplayers");
+ this.p.a("" + this.h);
+ this.p.a("hostport");
+ this.p.a("" + this.g);
+ this.p.a("hostip");
+ this.p.a(this.m);
+ this.p.a((int) 0);
+ this.p.a((int) 1);
+ this.p.a("player_");
+ this.p.a((int) 0);
+ String[] astring = this.r.getPlayers();
String[] astring1 = astring;
int j = astring.length;
for (int k = 0; k < j; ++k) {
String s = astring1[k];
- this.rulesResponse.writeString(s);
+ this.p.a(s);
}
- this.rulesResponse.write((int) 0);
+ this.p.a((int) 0);
+ */
+ // Pack plugins
+ java.util.List<com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation> plugins = java.util.Collections.emptyList();
+ org.bukkit.plugin.Plugin[] bukkitPlugins;
+ if (((DedicatedServer) this.getServer()).server.getQueryPlugins() && (bukkitPlugins = org.bukkit.Bukkit.getPluginManager().getPlugins()).length > 0) {
+ plugins = java.util.stream.Stream.of(bukkitPlugins)
+ .map(plugin -> com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation.of(plugin.getName(), plugin.getDescription().getVersion()))
+ .collect(java.util.stream.Collectors.toList());
+ }
+
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse queryResponse = com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.builder()
+ .motd(this.getMotd())
+ .map(this.getWorldName())
+ .currentPlayers(this.getServer().getPlayerCount())
+ .maxPlayers(this.getMaxPlayers())
+ .port(this.getServerPort())
+ .hostname(this.getServerHost())
+ .plugins(plugins)
+ .players(this.getServer().getPlayerNames())
+ .gameVersion(this.getServer().getServerVersion())
+ .serverVersion(org.bukkit.Bukkit.getServer().getName() + " on " + org.bukkit.Bukkit.getServer().getBukkitVersion())
+ .build();
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType queryType =
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryType.FULL;
+ com.destroystokyo.paper.event.server.GS4QueryEvent queryEvent =
+ new com.destroystokyo.paper.event.server.GS4QueryEvent(queryType, packet.getAddress(), queryResponse);
+ queryEvent.callEvent();
+ queryResponse = queryEvent.getResponse();
+ this.getCachedFullResponse().writeString("hostname");
+ this.getCachedFullResponse().writeString(queryResponse.getMotd());
+ this.getCachedFullResponse().writeString("gametype");
+ this.getCachedFullResponse().writeString("SMP");
+ this.getCachedFullResponse().writeString("game_id");
+ this.getCachedFullResponse().writeString("MINECRAFT");
+ this.getCachedFullResponse().writeString("version");
+ this.getCachedFullResponse().writeString(queryResponse.getGameVersion());
+ this.getCachedFullResponse().writeString("plugins");
+ java.lang.StringBuilder pluginsString = new java.lang.StringBuilder();
+ pluginsString.append(queryResponse.getServerVersion());
+ if (!queryResponse.getPlugins().isEmpty()) {
+ pluginsString.append(": ");
+ java.util.Iterator<com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation> iter = queryResponse.getPlugins().iterator();
+ while (iter.hasNext()) {
+ com.destroystokyo.paper.event.server.GS4QueryEvent.QueryResponse.PluginInformation info = iter.next();
+ pluginsString.append(info.getName());
+ if (info.getVersion() != null) {
+ pluginsString.append(' ').append(info.getVersion().replace(";", ","));
+ }
+ if (iter.hasNext()) {
+ pluginsString.append(';').append(' ');
+ }
+ }
+ }
+ this.getCachedFullResponse().writeString(pluginsString.toString());
+ this.getCachedFullResponse().writeString("map");
+ this.getCachedFullResponse().writeString(queryResponse.getMap());
+ this.getCachedFullResponse().writeString("numplayers");
+ this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getCurrentPlayers()));
+ this.getCachedFullResponse().writeString("maxplayers");
+ this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getMaxPlayers()));
+ this.getCachedFullResponse().writeString("hostport");
+ this.getCachedFullResponse().writeString(Integer.toString(queryResponse.getPort()));
+ this.getCachedFullResponse().writeString("hostip");
+ this.getCachedFullResponse().writeString(queryResponse.getHostname());
+ // The "meaningless data" start, copied from above
+ this.getCachedFullResponse().writeInt(0);
+ this.getCachedFullResponse().writeInt(1);
+ this.getCachedFullResponse().writeString("player_");
+ this.getCachedFullResponse().writeInt(0);
+ // "Meaningless data" end
+ queryResponse.getPlayers().forEach(this.getCachedFullResponse()::writeStringUnchecked);
+ this.getCachedFullResponse().writeInt(0);
+ // Paper end
return this.rulesResponse.toByteArray();
}
}

View File

@@ -1,54 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Wed, 6 May 2020 05:00:57 -0400
Subject: [PATCH] Handle Oversized Tile Entities in chunks
Splits out Extra Packets if too many TE's are encountered to prevent
creating too large of a packet to sed.
Co authored by Spottedleaf
diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacket.java
@@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
private boolean fullChunk;
public ClientboundLevelChunkPacket() {}
+ // Paper start
+ private final java.util.List<Packet> extraPackets = new java.util.ArrayList<>();
+ private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750);
+ @Override
+ public java.util.List<Packet> getExtraPackets() {
+ return extraPackets;
+ }
+ // Paper end
public ClientboundLevelChunkPacket(LevelChunk chunk, int includedSectionsMask) {
ChunkPos chunkcoordintpair = chunk.getPos();
@@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
this.availableSections = this.extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, includedSectionsMask);
this.blockEntitiesTags = Lists.newArrayList();
iterator = chunk.getBlockEntities().entrySet().iterator();
+ int totalTileEntities = 0; // Paper
while (iterator.hasNext()) {
entry = (Entry) iterator.next();
@@ -0,0 +0,0 @@ public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListe
int j = blockposition.getY() >> 4;
if (this.isFullChunk() || (includedSectionsMask & 1 << j) != 0) {
+ // Paper start - improve oversized chunk data packet handling
+ if (++totalTileEntities > TE_LIMIT) {
+ ClientboundBlockEntityDataPacket updatePacket = tileentity.getUpdatePacket();
+ if (updatePacket != null) {
+ this.extraPackets.add(updatePacket);
+ continue;
+ }
+ }
+ // Paper end
CompoundTag nbttagcompound = tileentity.getUpdateTag();
if (tileentity instanceof SkullBlockEntity) { SkullBlockEntity.sanitizeTileEntityUUID(nbttagcompound); } // Paper

View File

@@ -1,48 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MisterVector <whizkid3000@hotmail.com>
Date: Fri, 26 Oct 2018 21:31:00 -0700
Subject: [PATCH] Implement PlayerPostRespawnEvent
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -0,0 +0,0 @@ public abstract class PlayerList {
// this.a(entityplayer1, entityplayer, worldserver1); // CraftBukkit - removed
boolean flag2 = false;
+ // Paper start
+ boolean isBedSpawn = false;
+ boolean isRespawn = false;
+ // Paper end
+
// CraftBukkit start - fire PlayerRespawnEvent
if (location == null) {
- boolean isBedSpawn = false;
+ // boolean isBedSpawn = false; // Paper - moved up
ServerLevel worldserver1 = this.server.getLevel(entityplayer.getRespawnDimension());
if (worldserver1 != null) {
Optional optional;
@@ -0,0 +0,0 @@ public abstract class PlayerList {
location = respawnEvent.getRespawnLocation();
if (!flag) entityplayer.reset(); // SPIGOT-4785
+ isRespawn = true; // Paper
} else {
location.setWorld(worldserver.getWorld());
}
@@ -0,0 +0,0 @@ public abstract class PlayerList {
if (entityplayer.connection.isDisconnected()) {
this.save(entityplayer);
}
+
+ // Paper start
+ if (isRespawn) {
+ cserver.getPluginManager().callEvent(new com.destroystokyo.paper.event.player.PlayerPostRespawnEvent(entityplayer.getBukkitEntity(), location, isBedSpawn));
+ }
+ // Paper end
+
// CraftBukkit end
return entityplayer1;
}

View File

@@ -1,45 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sat, 2 Mar 2019 16:12:35 -0500
Subject: [PATCH] MC-145260: Fix Whitelist On/Off inconsistency
mojang stored whitelist state in 2 places (Whitelist Object, PlayerList)
some things checked PlayerList, some checked object. This moves
everything to the Whitelist object.
https://github.com/PaperMC/Paper/issues/1880
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -0,0 +0,0 @@ import net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateTagsPacket;
import net.minecraft.resources.ResourceKey;
+import net.minecraft.server.MCUtil;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.PlayerAdvancements;
import net.minecraft.server.ServerScoreboard;
@@ -0,0 +0,0 @@ public abstract class PlayerList {
}
public boolean isWhitelisted(GameProfile gameprofile, org.bukkit.event.player.PlayerLoginEvent loginEvent) {
boolean isOp = this.ops.contains(gameprofile);
- boolean isWhitelisted = !this.doWhiteList || isOp || this.whitelist.contains(gameprofile);
+ boolean isWhitelisted = !this.isUsingWhitelist() || isOp || this.whitelist.contains(gameprofile);
final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event;
- event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(MCUtil.toBukkit(gameprofile), this.doWhiteList, isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage);
+ event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(MCUtil.toBukkit(gameprofile), this.isUsingWhitelist(), isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage);
event.callEvent();
if (!event.isWhitelisted()) {
if (loginEvent != null) {
@@ -0,0 +0,0 @@ public abstract class PlayerList {
MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main
MinecraftTimings.savePlayers.startTiming(); // Paper
for (int i = 0; i < this.players.size(); ++i) {
- this.savePlayerFile((EntityPlayer) this.players.get(i));
+ this.save((ServerPlayer) this.players.get(i));
}
MinecraftTimings.savePlayers.stopTiming(); // Paper
return null; }); // Paper - ensure main

View File

@@ -1,394 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Wed, 6 May 2020 04:53:35 -0400
Subject: [PATCH] Optimize Network Manager and add advanced packet support
Adds ability for 1 packet to bundle other packets to follow it
Adds ability for a packet to delay sending more packets until a state is ready.
Removes synchronization from sending packets
Removes processing packet queue off of main thread
- for the few cases where it is allowed, order is not necessary nor
should it even be happening concurrently in first place (handshaking/login/status)
Ensures packets sent asynchronously are dispatched on main thread
This helps ensure safety for ProtocolLib as packet listeners
are commonly accessing world state. This will allow you to schedule
a packet to be sent async, but itll be dispatched sync for packet
listeners to process.
This should solve some deadlock risks
Also adds Netty Channel Flush Consolidation to reduce the amount of flushing
Also avoids spamming closed channel exception by rechecking closed state in dispatch
and then catch exceptions and close if they fire.
Part of this commit was authored by: Spottedleaf
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -0,0 +0,0 @@ import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
+import net.minecraft.network.protocol.game.ClientboundBossEventPacket;
+import net.minecraft.network.protocol.game.ClientboundChatPacket;
+import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket;
import net.minecraft.network.protocol.game.ClientboundDisconnectPacket;
+import net.minecraft.network.protocol.game.ClientboundKeepAlivePacket;
+import net.minecraft.network.protocol.game.ClientboundSetTitlesPacket;
+import net.minecraft.server.MCUtil;
import net.minecraft.server.RunningOnDifferentThreadException;
+import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.server.network.ServerLoginPacketListenerImpl;
import net.minecraft.util.LazyLoadedValue;
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
public int protocolVersion;
public java.net.InetSocketAddress virtualHost;
private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush");
+ // Optimize network
+ public boolean isPending = true;
+ public boolean queueImmunity = false;
+ public ConnectionProtocol protocol;
// Paper end
public Connection(PacketFlow side) {
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
public void setProtocol(ConnectionProtocol state) {
+ protocol = state; // Paper
this.channel.attr(Connection.ATTRIBUTE_PROTOCOL).set(state);
this.channel.config().setAutoRead(true);
Connection.LOGGER.debug("Enabled auto read");
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
Validate.notNull(listener, "packetListener", new Object[0]);
this.packetListener = listener;
}
+ // Paper start
+ public ServerPlayer getPlayer() {
+ if (packetListener instanceof ServerGamePacketListenerImpl) {
+ return ((ServerGamePacketListenerImpl) packetListener).player;
+ } else {
+ return null;
+ }
+ }
+ private static class InnerUtil { // Attempt to hide these methods from ProtocolLib so it doesn't accidently pick them up.
+ private static java.util.List<Packet> buildExtraPackets(Packet packet) {
+ java.util.List<Packet> extra = packet.getExtraPackets();
+ if (extra == null || extra.isEmpty()) {
+ return null;
+ }
+ java.util.List<Packet> ret = new java.util.ArrayList<>(1 + extra.size());
+ buildExtraPackets0(extra, ret);
+ return ret;
+ }
+
+ private static void buildExtraPackets0(java.util.List<Packet> extraPackets, java.util.List<Packet> into) {
+ for (Packet extra : extraPackets) {
+ into.add(extra);
+ java.util.List<Packet> extraExtra = extra.getExtraPackets();
+ if (extraExtra != null && !extraExtra.isEmpty()) {
+ buildExtraPackets0(extraExtra, into);
+ }
+ }
+ }
+ // Paper start
+ private static boolean canSendImmediate(Connection networkManager, Packet<?> packet) {
+ return networkManager.isPending || networkManager.protocol != ConnectionProtocol.PLAY ||
+ packet instanceof ClientboundKeepAlivePacket ||
+ packet instanceof ClientboundChatPacket ||
+ packet instanceof ClientboundCommandSuggestionsPacket ||
+ packet instanceof ClientboundSetTitlesPacket ||
+ packet instanceof ClientboundBossEventPacket;
+ }
+ // Paper end
+ }
+ // Paper end
public void send(Packet<?> packet) {
this.send(packet, (GenericFutureListener) null);
}
public void send(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
- if (this.isConnected()) {
- this.flushQueue();
- this.sendPacket(packet, callback);
- } else {
- this.queue.add(new Connection.PacketHolder(packet, callback));
+ // Paper start - handle oversized packets better
+ boolean connected = this.isConnected();
+ if (!connected && !preparing) {
+ return; // Do nothing
+ }
+ packet.onPacketDispatch(getPlayer());
+ if (connected && (InnerUtil.canSendImmediate(this, packet) || (
+ MCUtil.isMainThread() && packet.isReady() && this.queue.isEmpty() &&
+ (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())
+ ))) {
+ this.dispatchPacket(packet, callback);
+ return;
}
+ // write the packets to the queue, then flush - antixray hooks there already
+ java.util.List<Packet> extraPackets = InnerUtil.buildExtraPackets(packet);
+ boolean hasExtraPackets = extraPackets != null && !extraPackets.isEmpty();
+ if (!hasExtraPackets) {
+ this.queue.add(new Connection.PacketHolder(packet, callback));
+ } else {
+ java.util.List<Connection.PacketHolder> packets = new java.util.ArrayList<>(1 + extraPackets.size());
+ packets.add(new Connection.PacketHolder(packet, null)); // delay the future listener until the end of the extra packets
+
+ for (int i = 0, len = extraPackets.size(); i < len;) {
+ Packet extra = extraPackets.get(i);
+ boolean end = ++i == len;
+ packets.add(new Connection.PacketHolder(extra, end ? callback : null)); // append listener to the end
+ }
+ this.queue.addAll(packets); // atomic
+ }
+ this.sendPacketQueue();
+ // Paper end
}
private void dispatchPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> genericFutureListener) { this.sendPacket(packet, genericFutureListener); } // Paper - OBFHELPER
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
this.channel.config().setAutoRead(false);
}
+ ServerPlayer player = getPlayer(); // Paper
if (this.channel.eventLoop().inEventLoop()) {
if (enumprotocol != enumprotocol1) {
this.setProtocol(enumprotocol);
}
+ // Paper start
+ if (!isConnected()) {
+ packet.onPacketDispatchFinish(player, null);
+ return;
+ }
+ try {
+ // Paper end
ChannelFuture channelfuture = this.channel.writeAndFlush(packet);
if (callback != null) {
channelfuture.addListener(callback);
}
+ // Paper start
+ if (packet.hasFinishListener()) {
+ channelfuture.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture));
+ }
+ // Paper end
channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
+ // Paper start
+ } catch (Exception e) {
+ LOGGER.error("NetworkException: " + player, e);
+ disconnect(new TranslatableComponent("disconnect.genericReason", "Internal Exception: " + e.getMessage()));;
+ packet.onPacketDispatchFinish(player, null);
+ }
+ // Paper end
} else {
this.channel.eventLoop().execute(() -> {
if (enumprotocol != enumprotocol1) {
this.setProtocol(enumprotocol);
}
+ // Paper start
+ if (!isConnected()) {
+ packet.onPacketDispatchFinish(player, null);
+ return;
+ }
+ try {
+ // Paper end
ChannelFuture channelfuture1 = this.channel.writeAndFlush(packet);
+
if (callback != null) {
channelfuture1.addListener(callback);
}
+ // Paper start
+ if (packet.hasFinishListener()) {
+ channelfuture1.addListener((ChannelFutureListener) channelFuture -> packet.onPacketDispatchFinish(player, channelFuture));
+ }
+ // Paper end
channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
+ // Paper start
+ } catch (Exception e) {
+ LOGGER.error("NetworkException: " + player, e);
+ disconnect(new TranslatableComponent("disconnect.genericReason", "Internal Exception: " + e.getMessage()));;
+ packet.onPacketDispatchFinish(player, null);
+ }
+ // Paper end
});
}
}
- private void sendPacketQueue() { this.flushQueue(); } // Paper - OBFHELPER
- private void flushQueue() {
- if (this.channel != null && this.channel.isOpen()) {
- Queue queue = this.queue;
-
+ // Paper start - rewrite this to be safer if ran off main thread
+ private boolean sendPacketQueue() { return this.p(); } // OBFHELPER // void -> boolean
+ private boolean p() { // void -> boolean
+ if (!isConnected()) {
+ return true;
+ }
+ if (MCUtil.isMainThread()) {
+ return processQueue();
+ } else if (isPending) {
+ // Should only happen during login/status stages
synchronized (this.queue) {
- Connection.PacketHolder networkmanager_queuedpacket;
-
- while ((networkmanager_queuedpacket = (Connection.PacketHolder) this.queue.poll()) != null) {
- this.sendPacket(networkmanager_queuedpacket.packet, networkmanager_queuedpacket.listener);
- }
+ return this.processQueue();
+ }
+ }
+ return false;
+ }
+ private boolean processQueue() {
+ if (this.queue.isEmpty()) return true;
+ // If we are on main, we are safe here in that nothing else should be processing queue off main anymore
+ // But if we are not on main due to login/status, the parent is synchronized on packetQueue
+ java.util.Iterator<PacketHolder> iterator = this.queue.iterator();
+ while (iterator.hasNext()) {
+ Connection.PacketHolder queued = iterator.next(); // poll -> peek
+
+ // Fix NPE (Spigot bug caused by handleDisconnection())
+ if (queued == null) {
+ return true;
+ }
+ Packet<?> packet = queued.getPacket();
+ if (!packet.isReady()) {
+ return false;
+ } else {
+ iterator.remove();
+ this.dispatchPacket(packet, queued.getGenericFutureListener());
}
}
+ return true;
}
+ // Paper end
public void tick() {
- this.flushQueue();
+ this.p();
if (this.packetListener instanceof ServerLoginPacketListenerImpl) {
((ServerLoginPacketListenerImpl) this.packetListener).tick();
}
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
return this.address;
}
+ // Paper start
+ public void clearPacketQueue() {
+ ServerPlayer player = getPlayer();
+ queue.forEach(queuedPacket -> {
+ Packet<?> packet = queuedPacket.getPacket();
+ if (packet.hasFinishListener()) {
+ packet.onPacketDispatchFinish(player, null);
+ }
+ });
+ queue.clear();
+ } // Paper end
public void disconnect(Component disconnectReason) {
// Spigot Start
this.preparing = false;
+ clearPacketQueue(); // Paper
// Spigot End
if (this.channel.isOpen()) {
this.channel.close(); // We can't wait as this may be called from an event loop.
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
public void handleDisconnection() {
if (this.channel != null && !this.channel.isOpen()) {
if (this.disconnectionHandled) {
- Connection.LOGGER.warn("handleDisconnection() called twice");
+ //NetworkManager.LOGGER.warn("handleDisconnection() called twice"); // Paper - Do not log useless message
} else {
this.disconnectionHandled = true;
if (this.getDisconnectedReason() != null) {
@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
} else if (this.getPacketListener() != null) {
this.getPacketListener().a(new TranslatableComponent("multiplayer.disconnect.generic"));
}
- this.queue.clear(); // Free up packet queue.
+ clearPacketQueue(); // Paper
// Paper start - Add PlayerConnectionCloseEvent
final PacketListener packetListener = this.getPacketListener();
if (packetListener instanceof ServerGamePacketListenerImpl) {
diff --git a/src/main/java/net/minecraft/network/protocol/Packet.java b/src/main/java/net/minecraft/network/protocol/Packet.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/protocol/Packet.java
+++ b/src/main/java/net/minecraft/network/protocol/Packet.java
@@ -0,0 +0,0 @@
package net.minecraft.network.protocol;
+import io.netty.channel.ChannelFuture; // Paper
import java.io.IOException;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.PacketListener;
@@ -0,0 +0,0 @@ public interface Packet<T extends PacketListener> {
void handle(T listener);
// Paper start
+
+ /**
+ * @param player Null if not at PLAY stage yet
+ */
+ default void onPacketDispatch(@javax.annotation.Nullable EntityPlayer player) {}
+
+ /**
+ * @param player Null if not at PLAY stage yet
+ * @param future Can be null if packet was cancelled
+ */
+ default void onPacketDispatchFinish(@javax.annotation.Nullable EntityPlayer player, @javax.annotation.Nullable ChannelFuture future) {}
+ default boolean hasFinishListener() { return false; }
+ default boolean isReady() { return true; }
+ default java.util.List<Packet> getExtraPackets() { return null; }
default boolean packetTooLarge(NetworkManager manager) {
return false;
}
diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
@@ -0,0 +0,0 @@ import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.flush.FlushConsolidationHandler; // Paper
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.io.IOException;
import java.net.InetAddress;
@@ -0,0 +0,0 @@ public class ServerConnectionListener {
private final List<Connection> connections = Collections.synchronizedList(Lists.newArrayList());
// Paper start - prevent blocking on adding a new network manager while the server is ticking
private final java.util.Queue<Connection> pending = new java.util.concurrent.ConcurrentLinkedQueue<>();
+ private static final boolean disableFlushConsolidation = Boolean.getBoolean("Paper.disableFlushConsolidate"); // Paper
private void addPending() {
Connection manager = null;
while ((manager = pending.poll()) != null) {
connections.add(manager);
+ manager.isPending = false;
}
}
// Paper end
@@ -0,0 +0,0 @@ public class ServerConnectionListener {
;
}
+ if (!disableFlushConsolidation) channel.pipeline().addFirst(new FlushConsolidationHandler()); // Paper
channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this)).addLast("splitter", new Varint21FrameDecoder()).addLast("decoder", new PacketDecoder(PacketFlow.SERVERBOUND)).addLast("prepender", new Varint21LengthFieldPrepender()).addLast("encoder", new PacketEncoder(PacketFlow.CLIENTBOUND));
int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond();
Object object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND);

View File

@@ -1,19 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zach Brown <1254957+zachbr@users.noreply.github.com>
Date: Mon, 4 Mar 2019 02:23:28 -0500
Subject: [PATCH] Set Zombie last tick at start of drowning process
Fixes GH-1887
diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
@@ -0,0 +0,0 @@ public class Zombie extends Monster {
++this.inWaterTime;
if (this.inWaterTime >= 600) {
this.startUnderWaterConversion(300);
+ this.lastTick = MinecraftServer.currentTick; // Paper - Make sure this is set at start of process - GH-1887
}
} else {
this.inWaterTime = -1;

View File

@@ -1,43 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: AgentTroll <woodyc40@gmail.com>
Date: Fri, 22 Mar 2019 22:24:03 -0700
Subject: [PATCH] Update entity Metadata for all tracked players
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -0,0 +0,0 @@ public class ServerEntity {
return ClientboundMoveEntityPacket.packetToEntity(this.xp, this.yp, this.zp);
}
+ // Paper start - Add broadcast method
+ void broadcast(Packet<?> packet) {
+ this.getPacketConsumer().accept(packet);
+ }
+ // Paper end
+
private void broadcastAndSend(Packet<?> packet) {
this.broadcast.accept(packet);
if (this.entity instanceof ServerPlayer) {
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener {
if (event.isCancelled() || this.player.inventory.getSelected() == null || this.player.inventory.getSelected().getItem() != origItem) {
// Refresh the current entity metadata
- this.send(new ClientboundSetEntityDataPacket(entity.getId(), entity.getEntityData(), true));
+ // Paper start - update entity for all players
+ ClientboundSetEntityDataPacket packet1 = new ClientboundSetEntityDataPacket(entity.getId(), entity.getEntityData(), true);
+ if (entity.tracker != null) {
+ entity.tracker.broadcast(packet1);
+ } else {
+ this.send(packet1);
+ }
+ // Paper end
}
if (event.isCancelled()) {

View File

@@ -1,32 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Riley Park <rileysebastianpark@gmail.com>
Date: Wed, 20 Mar 2019 21:19:29 -0700
Subject: [PATCH] Use proper max length when serialising BungeeCord text
component
diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundChatPacket.java
@@ -0,0 +0,0 @@ import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
public class ClientboundChatPacket implements Packet<ClientGamePacketListener> {
-
+ private static final int MAX_LENGTH = Short.MAX_VALUE * 8 + 8; // Paper
private Component message;
public net.kyori.adventure.text.Component adventure$message; // Paper
public net.md_5.bungee.api.chat.BaseComponent[] components; // Spigot
@@ -0,0 +0,0 @@ public class ClientboundChatPacket implements Packet<ClientGamePacketListener> {
//packetdataserializer.a(net.md_5.bungee.chat.ComponentSerializer.toString(components)); // Paper - comment, replaced with below
// Paper start - don't nest if we don't need to so that we can preserve formatting
if (this.components.length == 1) {
- buf.writeByteArray(net.md_5.bungee.chat.ComponentSerializer.toString(this.components[0]));
+ buf.writeUtf(net.md_5.bungee.chat.ComponentSerializer.toString(this.components[0]), MAX_LENGTH); // Paper - use proper max length
} else {
- buf.writeByteArray(net.md_5.bungee.chat.ComponentSerializer.toString(this.components));
+ buf.writeUtf(net.md_5.bungee.chat.ComponentSerializer.toString(this.components), MAX_LENGTH); // Paper - use proper max length
}
// Paper end
} else {