Skip to content

Instantly share code, notes, and snippets.

@stephan-gh
Last active February 29, 2024 20:05
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save stephan-gh/5890483 to your computer and use it in GitHub Desktop.
Save stephan-gh/5890483 to your computer and use it in GitHub Desktop.
API to modify some advanced Minecraft server list data.
/*
* ServerListAPI - API to modify some advanced Minecraft server list data
* Copyright (C) 2013 Minecrell
* You are not allowed to use this API to fake online players on a production server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import java.net.InetAddress;
import java.util.HashSet;
import java.util.Set;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerListPingEvent;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.GamePhase;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
/**
* API to modify some advanced Minecraft server list data.
* If you want to use it with ProtocolLib you have to add ProtocolLib to (soft)depend in your plugin.yml file.
* @author Minecrell
*/
public class ServerListAPI {
private final Plugin plugin;
private Integer onlinePlayers, maxPlayers, snapshotVersion;
private String motd, serverVersion;
private boolean hideServerList, lockBannedIPs;
private final Set<InetAddress> lockedIPs = new HashSet<InetAddress>();
/**
* Creates a new instance of the API.
* @param plugin Plugin instance of the Plugin using this API.
* @see Plugin
*/
public ServerListAPI(final Plugin plugin) {
this.plugin = plugin;
plugin.getServer().getPluginManager().registerEvents(new ServerListListener(), plugin);
final Plugin pLib = plugin.getServer().getPluginManager().getPlugin("ProtocolLib");
if (pLib != null && (pLib instanceof com.comphenix.protocol.ProtocolLibrary)) {
new ServerListPacketAdapter();
}
}
/**
* Gets the plugin instance of this API.
* @return Plugin instance
* @see Plugin
*/
public Plugin getPlugin() {
return plugin;
}
/**
* Resets all changed values on this API.
* @return This API object.
*/
public ServerListAPI resetData() {
this.setOnlinePlayers(null);
this.setMaxPlayers(null);
this.setSnapshotVersion(null);
this.setServerVersion(null);
this.setMotd(null);
return this;
}
/**
* Gets the changed online player count.
* @return Online player count, or null if not changed.
*/
public Integer getOnlinePlayers() {
return onlinePlayers;
}
/**
* Sets the online player count for the server.
* @param onlinePlayers New online player count or null to reset it.
* @return This API object.
*/
public ServerListAPI setOnlinePlayers(final Integer onlinePlayers) {
this.onlinePlayers = onlinePlayers;
return this;
}
/**
* Gets the changed max player count.
* @return Max player count, or null if not changed.
*/
public Integer getMaxPlayers() {
return maxPlayers;
}
/**
* Sets the max player count for the server.
* @param maxPlayers New max player count or null to reset it.
* @return This API object.
*/
public ServerListAPI setMaxPlayers(final Integer maxPlayers) {
this.maxPlayers = maxPlayers;
return this;
}
/**
* Gets the changed snapshot version. This should only be changed if you
* want to display a custom server version. Remember, nobody can join the
* server without a mod or direct connect!
* @return Snapshot version or null if not changed.
*/
public Integer getSnapshotVersion() {
return snapshotVersion;
}
/**
* Sets the snapshot version for the server.
* @param snapshotVersion New snapshot version or null to reset it.
* @see #getSnapshotVersion()
* @return This API object.
*/
public ServerListAPI setSnapshotVersion(final Integer snapshotVersion) {
if (snapshotVersion != null && snapshotVersion < 0)
throw new IllegalArgumentException("Snapshot version can't be smaller than null.");
this.snapshotVersion = snapshotVersion;
return this;
}
/**
* Gets the changed server version string. The server version is displayed
* next to the server ping (or above the player count) when you look at your
* server list with a newer or older Minecraft version.
* @see #getSnapshotVersion()
* @return Server version or null if not changed.
*/
public String getServerVersion() {
return serverVersion;
}
/**
* Sets the server version for the server. The server version can contain
* colors.
* @param version Server version string or null to reset it.
* @see #getServerVersion()
* @return This API object.
*/
public ServerListAPI setServerVersion(final String version) {
serverVersion = version;
return this;
}
/**
* Sets the server version for the server. The server version can contain
* colors.
* @param version Server version string or null to reset it.
* @param changeSnapshotVersion If the snapshot version should be set automatically.
* @see #setServerVersion(String)
* @return This API object.
*/
public ServerListAPI setServerVersion(final String version, final boolean changeSnapshotVersion) {
this.setServerVersion(version);
if (changeSnapshotVersion)
snapshotVersion = 255;
return this;
}
/**
* Gets the changed <i>Message of the day</i> (MOTD).
* @return Motd or null if not changed.
*/
public String getMotd() {
return motd;
}
/**
* Sets the Motd for the server.
* @param motd Motd or null to reset it.
* @see #getMotd()
* @return This API object.
*/
public ServerListAPI setMotd(final String motd) {
this.motd = motd;
return this;
}
/**
* If this is true, the server will show as <i>Communication Error</i> in the server list for everyone.
* @return Hide server list entry for everyone?
*/
public boolean hideServerList() {
return hideServerList;
}
/**
* Sets whether the server should show as <i>Communication Error</i> in the server list for everyone.
* @param hideServerList Hide server list entry for everyone?
* @return This API object.
*/
public ServerListAPI setHideServerList(final boolean hideServerList) {
this.hideServerList = hideServerList;
return this;
}
/**
* If this is true, the API will automatically hide the server status in the server list for every banned IP.
* @return Lock banned IPs?
*/
public boolean lockBannedIPs() {
return lockBannedIPs;
}
/**
* Sets whether the API should automatically hide the server status in the server list for every banned IP.
* @param lockBannedIPs Hide server status for banned IPs?
* @return This API object.
*/
public ServerListAPI setLockBannedIPs(final boolean lockBannedIPs) {
this.lockBannedIPs = lockBannedIPs;
return this;
}
/**
* Lock an IP, so it won't see the server status in the server list anymore. (Server will show as <i>Communication Error</i>)
* @param address IP address of the player or service
* @return This API object.
*/
public ServerListAPI lockIP(final InetAddress address) {
lockedIPs.add(address);
return this;
}
/**
* Unlock an IP, so it will see the server status in the server list again.
* @param address IP address of the player or service
* @return This API object.
*/
public ServerListAPI unlockIP(final InetAddress address) {
lockedIPs.remove(address);
return this;
}
/**
* Returns the set containing all locked IPs for the server list.
* @return Set with all locked IPs
*/
public Set<InetAddress> getLockedIPs() {
return lockedIPs;
}
/**
* Resets all locked IPs. They will all see the server status in the server list again.
* @return This API object.
*/
public ServerListAPI resetLockedIPs() {
lockedIPs.clear();
return this;
}
private class ServerListListener implements Listener {
@EventHandler
public void onServerListPing(final ServerListPingEvent event) {
if (maxPlayers != null) event.setMaxPlayers(maxPlayers);
if (motd != null) event.setMotd(motd);
}
}
private class ServerListPacketAdapter extends PacketAdapter {
private static final char packetSeperator = '\0';
private final Splitter packetSplitter = Splitter.on(packetSeperator);
private final Joiner packetJoiner = Joiner.on(packetSeperator);
private ServerListPacketAdapter() {
super(ServerListAPI.this.plugin, ConnectionSide.SERVER_SIDE, ListenerPriority.NORMAL, GamePhase.LOGIN, Packets.Server.KICK_DISCONNECT);
ProtocolLibrary.getProtocolManager().addPacketListener(this);
}
@Override
public void onPacketSending(final PacketEvent event) {
if (event.getPacketID() != Packets.Server.KICK_DISCONNECT) return;
final PacketContainer packet = event.getPacket();
final String[] data = Iterables.toArray(packetSplitter.split(packet.getStrings().read(0)), String.class);
if (data.length != 6) return;
final InetAddress ip = event.getPlayer().getAddress().getAddress();
if (hideServerList || (lockBannedIPs && plugin.getServer().getIPBans().contains(ip.getHostAddress())) || lockedIPs.contains(ip)) {
packet.getStrings().write(0, null); return;
}
if (onlinePlayers != null) data[4] = onlinePlayers.toString();
if (snapshotVersion != null) data[1] = snapshotVersion.toString();
if (serverVersion != null) data[2] = serverVersion;
packet.getStrings().write(0, packetJoiner.join(data));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment