package com.comphenix.protocol.wrappers.ping; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Optional; import java.util.concurrent.Semaphore; import com.comphenix.protocol.events.InternalStructure; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.utility.MinecraftProtocolVersion; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.wrappers.*; import com.google.common.collect.ImmutableList; import org.bukkit.Bukkit; public final class ServerPingRecord implements ServerPingImpl { private static Class SERVER_PING; private static Class PLAYER_SAMPLE_CLASS; private static Class SERVER_DATA_CLASS; private static WrappedChatComponent DEFAULT_DESCRIPTION; private static ConstructorAccessor PING_CTOR; private static EquivalentConverter> PROFILE_LIST_CONVERTER; private static boolean initialized = false; private static final Object lock = new Object(); private static void initialize() { if (initialized) { return; } synchronized (lock) { // may have been initialized while waiting for the lock if (initialized) { return; } try { SERVER_PING = MinecraftReflection.getServerPingClass(); PLAYER_SAMPLE_CLASS = MinecraftReflection.getServerPingPlayerSampleClass(); SERVER_DATA_CLASS = MinecraftReflection.getServerPingServerDataClass(); PING_CTOR = Accessors.getConstructorAccessor(SERVER_PING.getConstructors()[0]); DATA_WRAPPER = AutoWrapper.wrap(ServerData.class, SERVER_DATA_CLASS); SAMPLE_WRAPPER = AutoWrapper.wrap(PlayerSample.class, PLAYER_SAMPLE_CLASS); FAVICON_WRAPPER = AutoWrapper.wrap(Favicon.class, MinecraftReflection.getMinecraftClass("network.protocol.status.ServerPing$a")); PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(BukkitConverters.getWrappedGameProfileConverter()); DEFAULT_DESCRIPTION = WrappedChatComponent.fromLegacyText("A Minecraft Server"); } catch (Exception ex) { throw new RuntimeException("Failed to initialize Server Ping", ex); } finally { initialized = true; } } } public static final class PlayerSample { public int max; public int online; public Object sample; public PlayerSample(int max, int online, Object sample) { this.max = max; this.online = online; this.sample = sample; } public PlayerSample() { this(0, 0, null); } } public static final class ServerData { public String name; public int protocol; public ServerData(String name, int protocol) { this.name = name; this.protocol = protocol; } public ServerData() { this("", 0); } } static final byte[] EMPTY_FAVICON = new byte[0]; public static final class Favicon { public byte[] iconBytes; public Favicon(byte[] iconBytes) { this.iconBytes = iconBytes; } public Favicon() { this(EMPTY_FAVICON); } } private static AutoWrapper SAMPLE_WRAPPER; private static AutoWrapper DATA_WRAPPER; private static AutoWrapper FAVICON_WRAPPER; private WrappedChatComponent description; private PlayerSample playerSample; private ServerData serverData; private Favicon favicon; private boolean enforceSafeChat; private boolean playersVisible = true; private static ServerData defaultData() { String name = MinecraftVersion.getCurrentVersion().toString(); int protocol = MinecraftProtocolVersion.getCurrentVersion(); return new ServerData(name, protocol); } private static PlayerSample defaultSample() { int max = Bukkit.getMaxPlayers(); int online = Bukkit.getOnlinePlayers().size(); return new PlayerSample(max, online, null); } private static Favicon defaultFavicon() { return new Favicon(); } public ServerPingRecord(Object handle) { initialize(); StructureModifier modifier = new StructureModifier<>(handle.getClass()).withTarget(handle); InternalStructure structure = new InternalStructure(handle, modifier); this.description = structure.getChatComponents().readSafely(0); StructureModifier> optionals = structure.getOptionals(Converters.passthrough(Object.class)); Optional sampleHandle = optionals.readSafely(0); this.playerSample = sampleHandle.isPresent() ? SAMPLE_WRAPPER.wrap(sampleHandle.get()) : defaultSample(); Optional dataHandle = optionals.readSafely(1); this.serverData = dataHandle.isPresent() ? DATA_WRAPPER.wrap(dataHandle.get()) : defaultData(); Optional faviconHandle = optionals.readSafely(2); this.favicon = faviconHandle.isPresent() ? FAVICON_WRAPPER.wrap(faviconHandle.get()) : defaultFavicon(); this.enforceSafeChat = structure.getBooleans().readSafely(0); } public ServerPingRecord() { initialize(); this.description = DEFAULT_DESCRIPTION; this.playerSample = defaultSample(); this.serverData = defaultData(); this.favicon = defaultFavicon(); } @Override public WrappedChatComponent getMotD() { return description; } @Override public void setMotD(WrappedChatComponent description) { this.description = description; } @Override public int getPlayersMaximum() { return playerSample.max; } @Override public void setPlayersMaximum(int maxPlayers) { playerSample.max = maxPlayers; } @Override public int getPlayersOnline() { return playerSample.online; } @Override public void setPlayersOnline(int onlineCount) { playerSample.online = onlineCount; } @Override public ImmutableList getPlayers() { if (playerSample.sample == null) { return ImmutableList.of(); } List list = PROFILE_LIST_CONVERTER.getSpecific(playerSample.sample); if (list == null) { return ImmutableList.of(); } return ImmutableList.copyOf(list); } @Override public void setPlayers(Iterable playerSample) { if (playerSample == null) { this.playerSample.sample = null; return; } List list = Converters.toList(playerSample); this.playerSample.sample = PROFILE_LIST_CONVERTER.getGeneric(list); } @Override public String getVersionName() { return serverData.name; } @Override public void setVersionName(String versionName) { serverData.name = versionName; } @Override public int getVersionProtocol() { return serverData.protocol; } @Override public void setVersionProtocol(int protocolVersion) { serverData.protocol = protocolVersion; } @Override public String getFavicon() { return new String(favicon.iconBytes, StandardCharsets.UTF_8); } @Override public void setFavicon(String favicon) { this.favicon.iconBytes = favicon.getBytes(StandardCharsets.UTF_8); } @Override public boolean isEnforceSecureChat() { return enforceSafeChat; } @Override public void setEnforceSecureChat(boolean safeChat) { this.enforceSafeChat = safeChat; } @Override public void resetPlayers() { this.playerSample = defaultSample(); } @Override public void resetVersion() { this.serverData = defaultData(); } @Override public boolean arePlayersVisible() { return playersVisible; } @Override public void setPlayersVisible(boolean visible) { this.playersVisible = visible; } @Override public Object getHandle() { WrappedChatComponent wrappedDescription = description != null ? description : DEFAULT_DESCRIPTION; Object descHandle = wrappedDescription.getHandle(); Optional playersHandle = Optional.ofNullable(playerSample != null ? SAMPLE_WRAPPER.unwrap(playerSample) : null); Optional versionHandle = Optional.ofNullable(serverData != null ? DATA_WRAPPER.unwrap(serverData) : null); Optional favHandle = Optional.ofNullable(favicon != null ? FAVICON_WRAPPER.unwrap(favicon) : null); return PING_CTOR.invoke(descHandle, playersHandle, versionHandle, favHandle, enforceSafeChat); } }