From 312043ac022d72e01d999013099afc1cd0956e21 Mon Sep 17 00:00:00 2001 From: Sotr Date: Mon, 25 Mar 2019 17:59:41 +0800 Subject: [PATCH] Upstream Paper --- .../destroystokyo/paper/PaperWorldConfig.java | 10 + .../paper/PaperWorldEntityList.java | 6 +- .../minecraft/server/ChunkRegionLoader.java | 2 +- .../java/net/minecraft/server/EntityItem.java | 3 +- .../java/net/minecraft/server/ItemSign.java | 25 + .../java/net/minecraft/server/ItemStack.java | 6 + .../net/minecraft/server/MinecraftServer.java | 2 +- .../minecraft/server/MobSpawnerAbstract.java | 2 +- .../minecraft/server/PlayerConnection.java | 2 +- .../java/net/minecraft/server/PlayerList.java | 17 +- .../server/RemoteConnectionThread.java | 152 +++++++ .../server/RemoteStatusListener.java | 430 ++++++++++++++++++ .../minecraft/server/RemoteStatusReply.java | 53 +++ 13 files changed, 702 insertions(+), 8 deletions(-) create mode 100644 src/main/java/net/minecraft/server/ItemSign.java create mode 100644 src/main/java/net/minecraft/server/RemoteConnectionThread.java create mode 100644 src/main/java/net/minecraft/server/RemoteStatusListener.java create mode 100644 src/main/java/net/minecraft/server/RemoteStatusReply.java diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java index f259c4e51..fa1d88aa8 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -594,4 +594,14 @@ public class PaperWorldConfig { log("Using vanilla redstone algorithm."); } } + + public boolean countAllMobsForSpawning = false; + private void countAllMobsForSpawning() { + countAllMobsForSpawning = getBoolean("count-all-mobs-for-spawning", false); + if (countAllMobsForSpawning) { + log("Counting all mobs for spawning. Mob farms may reduce natural spawns elsewhere in world."); + } else { + log("Using improved mob spawn limits (Only Natural Spawns impact spawn limits for more natural spawns)"); + } + } } diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldEntityList.java b/src/main/java/com/destroystokyo/paper/PaperWorldEntityList.java index 8ad00c7d1..a5a63f800 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldEntityList.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldEntityList.java @@ -92,7 +92,11 @@ public class PaperWorldEntityList extends ArrayList { public void updateEntityCount(Entity entity, int amt) { // Only count natural spawns so that mob - if (!(entity instanceof IAnimal) || entity.spawnReason != SpawnReason.NATURAL) return; + if (!(entity instanceof IAnimal) || ( + !world.paperConfig.countAllMobsForSpawning && + entity.spawnReason != SpawnReason.NATURAL && + entity.spawnReason != SpawnReason.CHUNK_GEN + )) return; if (entity instanceof EntityInsentient) { EntityInsentient entityinsentient = (EntityInsentient) entity; diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java index 51df075b4..53ae5d509 100644 --- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java +++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java @@ -1131,7 +1131,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { while (iterator.hasNext()) { Entity entity1 = (Entity) iterator.next(); - a(entity1, generatoraccess); + a(entity1, generatoraccess, reason); // Paper } } diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java index 03fee65e4..5f6e81372 100644 --- a/src/main/java/net/minecraft/server/EntityItem.java +++ b/src/main/java/net/minecraft/server/EntityItem.java @@ -58,6 +58,7 @@ public class EntityItem extends Entity { // CraftBukkit start - Use wall time for pickup and despawn timers int elapsedTicks = MinecraftServer.currentTick - this.lastTick; if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks; + this.pickupDelay = Math.max(0, this.pickupDelay); // Paper - don't go below 0 if (this.age != -32768) this.age += elapsedTicks; this.lastTick = MinecraftServer.currentTick; // CraftBukkit end @@ -147,6 +148,7 @@ public class EntityItem extends Entity { // CraftBukkit start - Use wall time for pickup and despawn timers int elapsedTicks = MinecraftServer.currentTick - this.lastTick; if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks; + this.pickupDelay = Math.max(0, this.pickupDelay); // Paper - don't go below 0 if (this.age != -32768) this.age += elapsedTicks; this.lastTick = MinecraftServer.currentTick; // CraftBukkit end @@ -363,7 +365,6 @@ public class EntityItem extends Entity { // CraftBukkit end if (this.pickupDelay == 0 && (this.g == null || 6000 - this.age <= 200 || this.g.equals(entityhuman.getUniqueID())) && entityhuman.inventory.pickup(itemstack)) { - entityhuman.receive(this, i); // Paper Start if (flyAtPlayer) { entityhuman.receive(this, i); diff --git a/src/main/java/net/minecraft/server/ItemSign.java b/src/main/java/net/minecraft/server/ItemSign.java new file mode 100644 index 000000000..11045ee1e --- /dev/null +++ b/src/main/java/net/minecraft/server/ItemSign.java @@ -0,0 +1,25 @@ +package net.minecraft.server; + +import javax.annotation.Nullable; + +public class ItemSign extends ItemBlockWallable { + + public static boolean openSign; // CraftBukkit + + public ItemSign(Item.Info item_info) { + super(Blocks.SIGN, Blocks.WALL_SIGN, item_info); + } + + protected boolean a(BlockPosition blockposition, World world, @Nullable EntityHuman entityhuman, ItemStack itemstack, IBlockData iblockdata) { + boolean flag = super.a(blockposition, world, entityhuman, itemstack, iblockdata); + + if (!world.isClientSide && !flag && entityhuman != null) { + // CraftBukkit start - SPIGOT-4678 + // entityhuman.openSign((TileEntitySign) world.getTileEntity(blockposition)); + ItemSign.openSign = true; + // CraftBukkit end + } + + return flag; + } +} diff --git a/src/main/java/net/minecraft/server/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java index 7827f692a..f81baa87d 100644 --- a/src/main/java/net/minecraft/server/ItemStack.java +++ b/src/main/java/net/minecraft/server/ItemStack.java @@ -301,6 +301,12 @@ public final class ItemStack { } } + // SPIGOT-4678 + if (this.item instanceof ItemSign && ItemSign.openSign) { + ItemSign.openSign = false; + entityhuman.openSign((TileEntitySign) world.getTileEntity(new BlockActionContext(itemactioncontext).getClickPosition())); + } + // SPIGOT-1288 - play sound stripped from ItemBlock if (this.item instanceof ItemBlock) { SoundEffectType soundeffecttype = ((ItemBlock) this.item).getBlock().getStepSound(); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index ce39ea09e..b93fccf91 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1113,7 +1113,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati this.methodProfiler.exit(); this.methodProfiler.enter("tracker"); - worldserver.getTracker().updatePlayers(); + if (playerList.players.size() > 0) worldserver.getTracker().updatePlayers(); // Paper - No players, why spend time tracking them? (See patch) this.methodProfiler.exit(); this.methodProfiler.exit(); worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions diff --git a/src/main/java/net/minecraft/server/MobSpawnerAbstract.java b/src/main/java/net/minecraft/server/MobSpawnerAbstract.java index b2d2de7f8..af38e5396 100644 --- a/src/main/java/net/minecraft/server/MobSpawnerAbstract.java +++ b/src/main/java/net/minecraft/server/MobSpawnerAbstract.java @@ -117,7 +117,7 @@ public abstract class MobSpawnerAbstract { } } // Paper end - Entity entity = ChunkRegionLoader.a(nbttagcompound, world, d3, d4, d5, false); + Entity entity = ChunkRegionLoader.spawnEntity(nbttagcompound, world, d3, d4, d5, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER); // Paper if (entity == null) { this.i(); diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java index c4edb5b85..65e76491b 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -1974,7 +1974,7 @@ public class PlayerConnection implements PacketListenerPlayIn, ITickable { if (event.isCancelled() || this.player.inventory.getItemInHand() == null || this.player.inventory.getItemInHand().getItem() != origItem) { // Refresh the current entity metadata - this.sendPacket(new PacketPlayOutEntityMetadata(entity.getId(), entity.datawatcher, true)); + entity.tracker.broadcast(new PacketPlayOutEntityMetadata(entity.getId(), entity.datawatcher, true)); // Paper - update entity for all players } if (event.isCancelled()) { diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java index b1630137e..012238ac2 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -196,7 +196,7 @@ public abstract class PlayerList { if (nbttagcompound != null && nbttagcompound.hasKeyOfType("RootVehicle", 10)) { NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("RootVehicle"); - Entity entity = ChunkRegionLoader.a(nbttagcompound1.getCompound("Entity"), worldserver, true); + Entity entity = ChunkRegionLoader.spawnEntity(nbttagcompound1.getCompound("Entity"), worldserver, true, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT); // Paper if (entity != null) { UUID uuid = nbttagcompound1.a("Attach"); @@ -663,9 +663,14 @@ public abstract class PlayerList { // this.a(entityplayer1, entityplayer, worldserver); // CraftBukkit - removed BlockPosition blockposition1; + // 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 CraftWorld cworld = (CraftWorld) this.server.server.getWorld(entityplayer.spawnWorld); if (cworld != null && blockposition != null) { blockposition1 = EntityHuman.getBed(cworld.getHandle(), blockposition, flag1); @@ -695,6 +700,7 @@ public abstract class PlayerList { location = respawnEvent.getRespawnLocation(); entityplayer.reset(); + isRespawn = true; // Paper } else { location.setWorld(server.getWorldServer(dimensionmanager).getWorld()); } @@ -757,6 +763,13 @@ public abstract class PlayerList { if (entityplayer.playerConnection.isDisconnected()) { this.savePlayerFile(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; } diff --git a/src/main/java/net/minecraft/server/RemoteConnectionThread.java b/src/main/java/net/minecraft/server/RemoteConnectionThread.java new file mode 100644 index 000000000..bcc36bbbf --- /dev/null +++ b/src/main/java/net/minecraft/server/RemoteConnectionThread.java @@ -0,0 +1,152 @@ +package net.minecraft.server; + +import com.google.common.collect.Lists; +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.ServerSocket; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public abstract class RemoteConnectionThread implements Runnable { + + private static final Logger h = LogManager.getLogger(); + private static final AtomicInteger i = new AtomicInteger(0); + protected boolean a; + protected IMinecraftServer b; protected IMinecraftServer getServer() { return b; } // Paper - OBFHELPER + protected final String c; + protected Thread d; + protected int e = 5; + protected List f = Lists.newArrayList(); + protected List g = Lists.newArrayList(); + + protected RemoteConnectionThread(IMinecraftServer iminecraftserver, String s) { + this.b = iminecraftserver; + this.c = s; + if (this.b.isDebugging()) { + this.c("Debugging is enabled, performance maybe reduced!"); + } + + } + + public synchronized void a() { + this.d = new Thread(this, this.c + " #" + RemoteConnectionThread.i.incrementAndGet()); + this.d.setUncaughtExceptionHandler(new ThreadNamedUncaughtExceptionHandler(RemoteConnectionThread.h)); + this.d.start(); + this.a = true; + } + + public boolean c() { + return this.a; + } + + protected void a(String s) { + this.b.g(s); + } + + protected void b(String s) { + this.b.info(s); + } + + protected void c(String s) { + this.b.warning(s); + } + + protected void d(String s) { + this.b.f(s); + } + + protected int getPlayerCount() { return d(); } // Paper - OBFHELPER + protected int d() { + return this.b.getPlayerCount(); + } + + protected void a(DatagramSocket datagramsocket) { + this.a("registerSocket: " + datagramsocket); + this.f.add(datagramsocket); + } + + protected boolean a(DatagramSocket datagramsocket, boolean flag) { + this.a("closeSocket: " + datagramsocket); + if (null == datagramsocket) { + return false; + } else { + boolean flag1 = false; + + if (!datagramsocket.isClosed()) { + datagramsocket.close(); + flag1 = true; + } + + if (flag) { + this.f.remove(datagramsocket); + } + + return flag1; + } + } + + protected boolean b(ServerSocket serversocket) { + return this.a(serversocket, true); + } + + protected boolean a(ServerSocket serversocket, boolean flag) { + this.a("closeSocket: " + serversocket); + if (null == serversocket) { + return false; + } else { + boolean flag1 = false; + + try { + if (!serversocket.isClosed()) { + serversocket.close(); + flag1 = true; + } + } catch (IOException ioexception) { + this.c("IO: " + ioexception.getMessage()); + } + + if (flag) { + this.g.remove(serversocket); + } + + return flag1; + } + } + + protected void e() { + this.a(false); + } + + protected void a(boolean flag) { + int i = 0; + Iterator iterator = this.f.iterator(); + + while (iterator.hasNext()) { + DatagramSocket datagramsocket = (DatagramSocket) iterator.next(); + + if (this.a(datagramsocket, false)) { + ++i; + } + } + + this.f.clear(); + iterator = this.g.iterator(); + + while (iterator.hasNext()) { + ServerSocket serversocket = (ServerSocket) iterator.next(); + + if (this.a(serversocket, false)) { + ++i; + } + } + + this.g.clear(); + if (flag && 0 < i) { + this.c("Force closed " + i + " sockets"); + } + + } +} diff --git a/src/main/java/net/minecraft/server/RemoteStatusListener.java b/src/main/java/net/minecraft/server/RemoteStatusListener.java new file mode 100644 index 000000000..fd981931b --- /dev/null +++ b/src/main/java/net/minecraft/server/RemoteStatusListener.java @@ -0,0 +1,430 @@ +package net.minecraft.server; + +import com.google.common.collect.Maps; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.PortUnreachableException; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import java.util.Map.Entry; + +public class RemoteStatusListener extends RemoteConnectionThread { + + private long h; + private int i; + private final int j; private int getServerPort() { return j; } // Paper - OBFHELPER + private final int k; private int getMaxPlayers() { return k; } // Paper - OBFHELPER + private final String l; private String getMotd() { return l; } // Paper - OBFHELPER + private final String m; private String getWorldName() { return m; } // Paper - OBFHELPER + private DatagramSocket n; + private final byte[] o = new byte[1460]; + private DatagramPacket p; + private final Map q; + private String r; private String getServerHost() { return r; } // Paper - OBFHELPER + private String s; + private final Map t; + private final long u; + private final RemoteStatusReply v; private RemoteStatusReply getCachedFullResponse() { return v; } // Paper - OBFHELPER + private long w; + + public RemoteStatusListener(IMinecraftServer iminecraftserver) { + super(iminecraftserver, "Query Listener"); + this.i = iminecraftserver.a("query.port", 0); + this.s = iminecraftserver.e(); + this.j = iminecraftserver.e_(); + this.l = iminecraftserver.m(); + this.k = iminecraftserver.getMaxPlayers(); + this.m = iminecraftserver.getWorld(); + this.w = 0L; + this.r = "0.0.0.0"; + if (!this.s.isEmpty() && !this.r.equals(this.s)) { + this.r = this.s; + } else { + this.s = "0.0.0.0"; + + try { + InetAddress inetaddress = InetAddress.getLocalHost(); + + this.r = inetaddress.getHostAddress(); + } catch (UnknownHostException unknownhostexception) { + this.c("Unable to determine local host IP, please set server-ip in '" + iminecraftserver.d_() + "' : " + unknownhostexception.getMessage()); + } + } + + if (0 == this.i) { + this.i = this.j; + this.b("Setting default query port to " + this.i); + iminecraftserver.a("query.port", (Object) this.i); + iminecraftserver.a("debug", (Object) false); + iminecraftserver.c_(); + } + + this.q = Maps.newHashMap(); + this.v = new RemoteStatusReply(1460); + this.t = Maps.newHashMap(); + this.u = (new Date()).getTime(); + } + + private void a(byte[] abyte, DatagramPacket datagrampacket) throws IOException { + this.n.send(new DatagramPacket(abyte, abyte.length, datagrampacket.getSocketAddress())); + } + + private boolean a(DatagramPacket datagrampacket) throws IOException { + byte[] abyte = datagrampacket.getData(); + int i = datagrampacket.getLength(); + SocketAddress socketaddress = datagrampacket.getSocketAddress(); + + this.a("Packet len " + i + " [" + socketaddress + "]"); + if (3 <= i && -2 == abyte[0] && -3 == abyte[1]) { + this.a("Packet '" + StatusChallengeUtils.a(abyte[2]) + "' [" + socketaddress + "]"); + switch (abyte[2]) { + case 0: + if (!this.c(datagrampacket)) { + this.a("Invalid challenge [" + socketaddress + "]"); + return false; + } else if (15 == i) { + this.a(this.b(datagrampacket), datagrampacket); + this.a("Rules [" + socketaddress + "]"); + } else { + RemoteStatusReply remotestatusreply = new RemoteStatusReply(1460); + + remotestatusreply.a((int) 0); + remotestatusreply.a(this.a(datagrampacket.getSocketAddress())); + /* Paper start - GS4 Query event + remotestatusreply.a(this.l); + remotestatusreply.a("SMP"); + remotestatusreply.a(this.m); + remotestatusreply.a(Integer.toString(this.d())); + remotestatusreply.a(Integer.toString(this.k)); + remotestatusreply.a((short) this.j); + remotestatusreply.a(this.r); + */ + 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.getPlayerCount()) + .maxPlayers(this.getMaxPlayers()) + .port(this.getServerPort()) + .hostname(this.getServerHost()) + .gameVersion(this.getServer().getVersion()) + .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, datagrampacket.getAddress(), queryResponse); + queryEvent.callEvent(); + queryResponse = queryEvent.getResponse(); + remotestatusreply.writeString(queryResponse.getMotd()); + remotestatusreply.writeString("SMP"); + 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.a(remotestatusreply.a(), datagrampacket); + this.a("Status [" + socketaddress + "]"); + } + default: + return true; + case 9: + this.d(datagrampacket); + this.a("Challenge [" + socketaddress + "]"); + return true; + } + } else { + this.a("Invalid packet [" + socketaddress + "]"); + return false; + } + } + + private byte[] b(DatagramPacket datagrampacket) throws IOException { + long i = SystemUtils.getMonotonicMillis(); + + if (i < this.w + 5000L) { + byte[] abyte = this.v.a(); + byte[] abyte1 = this.a(datagrampacket.getSocketAddress()); + + abyte[1] = abyte1[0]; + abyte[2] = abyte1[1]; + abyte[3] = abyte1[2]; + abyte[4] = abyte1[3]; + return abyte; + } else { + this.w = i; + this.v.b(); + this.v.a((int) 0); + this.v.a(this.a(datagrampacket.getSocketAddress())); + this.v.a("splitnum"); + this.v.a((int) 128); + this.v.a((int) 0); + /* Paper start - GS4 Query event + this.v.a("hostname"); + this.v.a(this.l); + this.v.a("gametype"); + this.v.a("SMP"); + this.v.a("game_id"); + this.v.a("MINECRAFT"); + this.v.a("version"); + this.v.a(this.b.getVersion()); + this.v.a("plugins"); + this.v.a(this.b.getPlugins()); + this.v.a("map"); + this.v.a(this.m); + this.v.a("numplayers"); + this.v.a("" + this.d()); + this.v.a("maxplayers"); + this.v.a("" + this.k); + this.v.a("hostport"); + this.v.a("" + this.j); + this.v.a("hostip"); + this.v.a(this.r); + this.v.a((int) 0); + this.v.a((int) 1); + this.v.a("player_"); + this.v.a((int) 0); + String[] astring = this.b.getPlayers(); + String[] astring1 = astring; + int j = astring.length; + + for (int k = 0; k < j; ++k) { + String s = astring1[k]; + + this.v.a(s); + } + + this.v.a((int) 0); + */ + // Pack plugins + java.util.List 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.getPlayerCount()) + .maxPlayers(this.getMaxPlayers()) + .port(this.getServerPort()) + .hostname(this.getServerHost()) + .plugins(plugins) + .players(this.getServer().getPlayers()) + .gameVersion(this.getServer().getVersion()) + .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, datagrampacket.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(": "); + Iterator 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().replaceAll(";", ",")); + } + 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.v.a(); + } + } + + private byte[] a(SocketAddress socketaddress) { + return ((RemoteStatusListener.RemoteStatusChallenge) this.t.get(socketaddress)).c(); + } + + private Boolean c(DatagramPacket datagrampacket) { + SocketAddress socketaddress = datagrampacket.getSocketAddress(); + + if (!this.t.containsKey(socketaddress)) { + return false; + } else { + byte[] abyte = datagrampacket.getData(); + + return ((RemoteStatusListener.RemoteStatusChallenge) this.t.get(socketaddress)).a() != StatusChallengeUtils.c(abyte, 7, datagrampacket.getLength()) ? false : true; + } + } + + private void d(DatagramPacket datagrampacket) throws IOException { + RemoteStatusListener.RemoteStatusChallenge remotestatuslistener_remotestatuschallenge = new RemoteStatusListener.RemoteStatusChallenge(datagrampacket); + + this.t.put(datagrampacket.getSocketAddress(), remotestatuslistener_remotestatuschallenge); + this.a(remotestatuslistener_remotestatuschallenge.b(), datagrampacket); + } + + private void f() { + if (this.a) { + long i = SystemUtils.getMonotonicMillis(); + + if (i >= this.h + 30000L) { + this.h = i; + Iterator iterator = this.t.entrySet().iterator(); + + while (iterator.hasNext()) { + Entry entry = (Entry) iterator.next(); + + if (((RemoteStatusListener.RemoteStatusChallenge) entry.getValue()).a(i)) { + iterator.remove(); + } + } + + } + } + } + + public void run() { + this.b("Query running on " + this.s + ":" + this.i); + this.h = SystemUtils.getMonotonicMillis(); + this.p = new DatagramPacket(this.o, this.o.length); + + try { + while (this.a) { + try { + this.n.receive(this.p); + this.f(); + this.a(this.p); + } catch (SocketTimeoutException sockettimeoutexception) { + this.f(); + } catch (PortUnreachableException portunreachableexception) { + ; + } catch (IOException ioexception) { + this.a((Exception) ioexception); + } + } + } finally { + this.e(); + } + + } + + public void a() { + if (!this.a) { + if (0 < this.i && 65535 >= this.i) { + if (this.g()) { + super.a(); + } + + } else { + this.c("Invalid query port " + this.i + " found in '" + this.b.d_() + "' (queries disabled)"); + } + } + } + + private void a(Exception exception) { + if (this.a) { + this.c("Unexpected exception, buggy JRE? (" + exception + ")"); + if (!this.g()) { + this.d("Failed to recover from buggy JRE, shutting down!"); + this.a = false; + } + + } + } + + private boolean g() { + try { + this.n = new DatagramSocket(this.i, InetAddress.getByName(this.s)); + this.a(this.n); + this.n.setSoTimeout(500); + return true; + } catch (SocketException socketexception) { + this.c("Unable to initialise query system on " + this.s + ":" + this.i + " (Socket): " + socketexception.getMessage()); + } catch (UnknownHostException unknownhostexception) { + this.c("Unable to initialise query system on " + this.s + ":" + this.i + " (Unknown Host): " + unknownhostexception.getMessage()); + } catch (Exception exception) { + this.c("Unable to initialise query system on " + this.s + ":" + this.i + " (E): " + exception.getMessage()); + } + + return false; + } + + class RemoteStatusChallenge { + + private final long time = (new Date()).getTime(); + private final int token; + private final byte[] identity; + private final byte[] e; + private final String f; + + public RemoteStatusChallenge(DatagramPacket datagrampacket) { + byte[] abyte = datagrampacket.getData(); + + this.identity = new byte[4]; + this.identity[0] = abyte[3]; + this.identity[1] = abyte[4]; + this.identity[2] = abyte[5]; + this.identity[3] = abyte[6]; + this.f = new String(this.identity, StandardCharsets.UTF_8); + this.token = (new Random()).nextInt(16777216); + this.e = String.format("\t%s%d\u0000", this.f, this.token).getBytes(StandardCharsets.UTF_8); + } + + public Boolean a(long i) { + return this.time < i; + } + + public int a() { + return this.token; + } + + public byte[] b() { + return this.e; + } + + public byte[] c() { + return this.identity; + } + } +} diff --git a/src/main/java/net/minecraft/server/RemoteStatusReply.java b/src/main/java/net/minecraft/server/RemoteStatusReply.java new file mode 100644 index 000000000..9e8c8b3df --- /dev/null +++ b/src/main/java/net/minecraft/server/RemoteStatusReply.java @@ -0,0 +1,53 @@ +package net.minecraft.server; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class RemoteStatusReply { + + private final ByteArrayOutputStream a; + private final DataOutputStream b; + + public RemoteStatusReply(int i) { + this.a = new ByteArrayOutputStream(i); + this.b = new DataOutputStream(this.a); + } + + public void a(byte[] abyte) throws IOException { + this.b.write(abyte, 0, abyte.length); + } + + public void writeString(String string) throws IOException { a(string); } // Paper - OBFHELPER + public void a(String s) throws IOException { + this.b.writeBytes(s); + this.b.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 { a(i); } // Paper - OBFHELPER + public void a(int i) throws IOException { + this.b.write(i); + } + + public void writeShort(short i) throws IOException { a(i); } // Paper - OBFHELPER + public void a(short short0) throws IOException { + this.b.writeShort(Short.reverseBytes(short0)); + } + + public byte[] a() { + return this.a.toByteArray(); + } + + public void b() { + this.a.reset(); + } +}