/* * Decompiled with CFR 0.152. * * Could not load the following classes: * javax.annotation.Nullable * org.bukkit.Bukkit * org.bukkit.GameMode * org.bukkit.Server * org.bukkit.entity.Entity * org.bukkit.entity.Player */ package de.maxhenkel.voicechat.voice.server; import de.maxhenkel.voicechat.Voicechat; import de.maxhenkel.voicechat.api.RawUdpPacket; import de.maxhenkel.voicechat.api.VoicechatSocket; import de.maxhenkel.voicechat.debug.CooldownTimer; import de.maxhenkel.voicechat.debug.VoicechatUncaughtExceptionHandler; import de.maxhenkel.voicechat.net.NetManager; import de.maxhenkel.voicechat.permission.PermissionManager; import de.maxhenkel.voicechat.plugins.PluginManager; import de.maxhenkel.voicechat.voice.common.AuthenticateAckPacket; import de.maxhenkel.voicechat.voice.common.AuthenticatePacket; import de.maxhenkel.voicechat.voice.common.ConnectionCheckAckPacket; import de.maxhenkel.voicechat.voice.common.ConnectionCheckPacket; import de.maxhenkel.voicechat.voice.common.GroupSoundPacket; import de.maxhenkel.voicechat.voice.common.KeepAlivePacket; import de.maxhenkel.voicechat.voice.common.LocationSoundPacket; import de.maxhenkel.voicechat.voice.common.MicPacket; import de.maxhenkel.voicechat.voice.common.NetworkMessage; import de.maxhenkel.voicechat.voice.common.Packet; import de.maxhenkel.voicechat.voice.common.PingPacket; import de.maxhenkel.voicechat.voice.common.PlayerSoundPacket; import de.maxhenkel.voicechat.voice.common.PlayerState; import de.maxhenkel.voicechat.voice.common.Secret; import de.maxhenkel.voicechat.voice.common.SoundPacket; import de.maxhenkel.voicechat.voice.common.Utils; import de.maxhenkel.voicechat.voice.server.ClientConnection; import de.maxhenkel.voicechat.voice.server.Group; import de.maxhenkel.voicechat.voice.server.PingManager; import de.maxhenkel.voicechat.voice.server.PlayerStateManager; import de.maxhenkel.voicechat.voice.server.ServerCategoryManager; import de.maxhenkel.voicechat.voice.server.ServerGroupManager; import de.maxhenkel.voicechat.voice.server.ServerPlayerManager; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.channels.AsynchronousCloseException; import java.util.Collection; import java.util.Map; import java.util.UUID; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; public class Server extends Thread { private final Map connections; private final Map unCheckedConnections; private final Map secrets; private final int port; private final org.bukkit.Server server; private final VoicechatSocket socket; private final ProcessThread processThread; private final BlockingQueue packetQueue; private final PingManager pingManager; private final PlayerStateManager playerStateManager; private final ServerGroupManager groupManager; private final ServerCategoryManager categoryManager; public Server() { int configPort = Voicechat.SERVER_CONFIG.voiceChatPort.get(); if (configPort < 0) { Voicechat.LOGGER.info("Using the Minecraft servers port as voice chat port", new Object[0]); this.port = Bukkit.getPort(); } else { this.port = configPort; } this.server = Bukkit.getServer(); this.socket = PluginManager.instance().getSocketImplementation(); this.connections = new ConcurrentHashMap(); this.unCheckedConnections = new ConcurrentHashMap(); this.secrets = new ConcurrentHashMap(); this.packetQueue = new LinkedBlockingQueue(); this.pingManager = new PingManager(this); this.playerStateManager = new PlayerStateManager(); this.groupManager = new ServerGroupManager(); this.categoryManager = new ServerCategoryManager(); this.setDaemon(true); this.setName("VoiceChatServerThread"); this.setUncaughtExceptionHandler(new VoicechatUncaughtExceptionHandler()); this.processThread = new ProcessThread(); this.processThread.start(); } @Override public void run() { try { String bindAddress = this.getBindAddress(); try { InetAddress.getByName(bindAddress); } catch (UnknownHostException e) { Voicechat.LOGGER.error("Failed to parse bind IP address '{}'", bindAddress, e); Voicechat.LOGGER.info("Binding to wildcard IP address", new Object[0]); bindAddress = ""; } this.socket.open(this.port, bindAddress); if (bindAddress.isEmpty()) { Voicechat.LOGGER.info("Voice chat server started at port {}", this.socket.getLocalPort()); } else { Voicechat.LOGGER.info("Voice chat server started at {}:{}", bindAddress, this.socket.getLocalPort()); } while (!this.socket.isClosed()) { try { this.packetQueue.add(this.socket.read()); } catch (Exception e) { if (e instanceof SocketException && e.getCause() instanceof AsynchronousCloseException || !Voicechat.debugMode()) continue; Voicechat.LOGGER.error("Failed to read from socket", e); } } } catch (Exception e) { Voicechat.LOGGER.error("Voice chat server error", e); } } private String getBindAddress() { String bindAddress; block8: { bindAddress = Voicechat.SERVER_CONFIG.voiceChatBindAddress.get(); if (bindAddress.trim().equals("*")) { bindAddress = ""; } else if (bindAddress.trim().isEmpty()) { try { bindAddress = Bukkit.getIp(); if (bindAddress.trim().isEmpty()) break block8; try { InetAddress address = InetAddress.getByName(bindAddress); if (address.isLoopbackAddress()) { bindAddress = ""; break block8; } Voicechat.LOGGER.info("Using server-ip as bind address: {}", bindAddress); } catch (Exception e) { Voicechat.LOGGER.warn("Invalid server-ip", e); bindAddress = ""; } } catch (Throwable t) { Voicechat.LOGGER.warn("Failed to get server-ip from server.properties - binding to wildcard address", t); } } } return bindAddress; } public Secret getSecret(UUID playerUUID) { if (this.hasSecret(playerUUID)) { return this.secrets.get(playerUUID); } Secret secret = Secret.generateNewRandomSecret(); this.secrets.put(playerUUID, secret); return secret; } @Nullable public Secret generateNewSecret(UUID playerUUID) { if (this.hasSecret(playerUUID)) { return null; } return this.getSecret(playerUUID); } public boolean hasSecret(UUID playerUUID) { return this.secrets.containsKey(playerUUID); } public void disconnectClient(UUID playerUUID) { this.connections.remove(playerUUID); this.unCheckedConnections.remove(playerUUID); this.secrets.remove(playerUUID); PluginManager.instance().onPlayerDisconnected(playerUUID); } public void close() { this.socket.close(); this.processThread.close(); PluginManager.instance().onServerStopped(); } public boolean isClosed() { return !this.processThread.running; } public void onMicPacket(UUID playerUuid, MicPacket packet) { Player player = this.server.getPlayer(playerUuid); if (player == null) { return; } if (!player.hasPermission(PermissionManager.SPEAK_PERMISSION)) { CooldownTimer.run("no-speak-" + playerUuid, () -> NetManager.sendStatusMessage(player, "message.voicechat.no_speak_permission", new String[0])); return; } PlayerState state = this.playerStateManager.getState(player.getUniqueId()); if (state == null) { return; } if (!PluginManager.instance().onMicPacket(player, state, packet)) { this.processMicPacket(player, state, packet); } } private void processMicPacket(Player player, PlayerState state, MicPacket packet) { if (state.hasGroup()) { Group group = this.groupManager.getGroup(state.getGroup()); this.processGroupPacket(state, player, packet); if (group == null || group.isOpen()) { this.processProximityPacket(state, player, packet); } return; } this.processProximityPacket(state, player, packet); } private void processGroupPacket(PlayerState senderState, Player sender, MicPacket packet) { UUID groupId = senderState.getGroup(); if (groupId == null) { return; } GroupSoundPacket groupSoundPacket = new GroupSoundPacket(senderState.getUuid(), senderState.getUuid(), packet.getData(), packet.getSequenceNumber(), null); for (PlayerState state : this.playerStateManager.getStates()) { Player p; if (!groupId.equals(state.getGroup()) || senderState.getUuid().equals(state.getUuid()) || (p = this.server.getPlayer(state.getUuid())) == null) continue; ClientConnection connection = this.getConnection(state.getUuid()); this.sendSoundPacket(sender, senderState, p, state, connection, groupSoundPacket, "group"); } } private void processProximityPacket(PlayerState senderState, Player sender, MicPacket packet) { UUID groupId = senderState.getGroup(); float distance = packet.isWhispering() ? Voicechat.SERVER_CONFIG.whisperDistance.get().floatValue() : Utils.getDefaultDistance(); distance = PluginManager.instance().getDistance(sender, senderState, packet, distance); SoundPacket soundPacket = null; String source = null; if (sender.getGameMode().equals((Object)GameMode.SPECTATOR)) { Player spectatingPlayer; Entity camera; if (Voicechat.SERVER_CONFIG.spectatorPlayerPossession.get().booleanValue() && (camera = sender.getSpectatorTarget()) instanceof Player && (spectatingPlayer = (Player)camera) != sender) { PlayerState receiverState = this.playerStateManager.getState(spectatingPlayer.getUniqueId()); if (receiverState == null) { return; } GroupSoundPacket groupSoundPacket = new GroupSoundPacket(senderState.getUuid(), senderState.getUuid(), packet.getData(), packet.getSequenceNumber(), null); ClientConnection connection = this.getConnection(receiverState.getUuid()); this.sendSoundPacket(sender, senderState, spectatingPlayer, receiverState, connection, groupSoundPacket, "spectator"); return; } if (Voicechat.SERVER_CONFIG.spectatorInteraction.get().booleanValue()) { soundPacket = new LocationSoundPacket(sender.getUniqueId(), sender.getUniqueId(), sender.getLocation(), packet.getData(), packet.getSequenceNumber(), distance, null); source = "spectator"; } } if (soundPacket == null) { soundPacket = new PlayerSoundPacket(sender.getUniqueId(), sender.getUniqueId(), packet.getData(), packet.getSequenceNumber(), packet.isWhispering(), distance, null); source = "proximity"; } this.broadcast(ServerPlayerManager.getPlayersInRange(sender.getWorld(), sender.getLocation(), this.getBroadcastRange(distance), p -> !p.getUniqueId().equals(sender.getUniqueId())), soundPacket, sender, senderState, groupId, source); } public void sendSoundPacket(@Nullable Player sender, @Nullable PlayerState senderState, Player receiver, PlayerState receiverState, @Nullable ClientConnection connection, SoundPacket soundPacket, String source) { PluginManager.instance().onListenerAudio(receiver.getUniqueId(), soundPacket); if (connection == null) { return; } if (receiverState.isDisabled() || receiverState.isDisconnected()) { return; } if (PluginManager.instance().onSoundPacket(sender, senderState, receiver, receiverState, soundPacket, source)) { return; } if (!receiver.hasPermission(PermissionManager.LISTEN_PERMISSION)) { CooldownTimer.run(String.format("no-listen-%s", receiver.getUniqueId()), 30000L, () -> NetManager.sendStatusMessage(receiver, "message.voicechat.no_listen_permission", new String[0])); return; } this.sendPacket(soundPacket, connection); } public double getBroadcastRange(float minRange) { double broadcastRange = Voicechat.SERVER_CONFIG.broadcastRange.get(); if (broadcastRange < 0.0) { broadcastRange = Voicechat.SERVER_CONFIG.voiceChatDistance.get() + 1.0; } return Math.max(broadcastRange, (double)minRange); } public void broadcast(Collection players, SoundPacket packet, @Nullable Player sender, @Nullable PlayerState senderState, @Nullable UUID groupId, String source) { for (Player player : players) { PlayerState state = this.playerStateManager.getState(player.getUniqueId()); if (state == null || state.hasGroup() && state.getGroup().equals(groupId)) continue; Group receiverGroup = null; if (state.hasGroup()) { receiverGroup = this.groupManager.getGroup(state.getGroup()); } if (receiverGroup != null && receiverGroup.isIsolated()) continue; ClientConnection connection = this.getConnection(state.getUuid()); this.sendSoundPacket(sender, senderState, player, state, connection, packet, source); } } private void sendKeepAlives() { long timestamp = System.currentTimeMillis(); this.connections.values().removeIf(connection -> { if (timestamp - connection.getLastKeepAliveResponse() >= (long)Voicechat.SERVER_CONFIG.keepAlive.get().intValue() * 10L) { this.secrets.remove(connection.getPlayerUUID()); Voicechat.LOGGER.info("Player {} timed out", connection.getPlayerUUID()); Player player = this.server.getPlayer(connection.getPlayerUUID()); if (player != null) { Voicechat.LOGGER.info("Reconnecting player {}", player.getName()); Voicechat.SERVER.initializePlayerConnection(player); } else { Voicechat.LOGGER.error("Reconnecting player {} failed (Could not find player)", connection.getPlayerUUID()); } this.playerStateManager.onPlayerVoicechatDisconnect(connection.getPlayerUUID()); PluginManager.instance().onPlayerDisconnected(connection.getPlayerUUID()); return true; } return false; }); for (ClientConnection connection2 : this.connections.values()) { this.sendPacket(new KeepAlivePacket(), connection2); } } @Nullable public ClientConnection getSender(NetworkMessage message) { return this.connections.values().stream().filter(connection -> connection.getAddress().equals(message.getAddress())).findAny().orElse(null); } @Nullable public ClientConnection getUnconnectedSender(NetworkMessage message) { return this.unCheckedConnections.values().stream().filter(connection -> connection.getAddress().equals(message.getAddress())).findAny().orElse(null); } public Map getConnections() { return this.connections; } @Nullable public ClientConnection getConnection(UUID playerID) { return this.connections.get(playerID); } public VoicechatSocket getSocket() { return this.socket; } public int getPort() { return this.socket.getLocalPort(); } public boolean sendPacket(Packet packet, ClientConnection connection) { try { this.sendPacketRaw(packet, connection); return true; } catch (Exception e) { Voicechat.LOGGER.error("Failed to send voice chat packet to {}", connection.getPlayerUUID()); return false; } } public void sendPacketRaw(Packet packet, ClientConnection connection) throws Exception { connection.send(this, new NetworkMessage(packet)); } public PingManager getPingManager() { return this.pingManager; } public PlayerStateManager getPlayerStateManager() { return this.playerStateManager; } public ServerGroupManager getGroupManager() { return this.groupManager; } public ServerCategoryManager getCategoryManager() { return this.categoryManager; } private class ProcessThread extends Thread { private boolean running = true; private long lastKeepAlive = 0L; public ProcessThread() { this.setDaemon(true); this.setName("VoiceChatPacketProcessingThread"); this.setUncaughtExceptionHandler(new VoicechatUncaughtExceptionHandler()); } @Override public void run() { while (this.running) { try { NetworkMessage message; RawUdpPacket rawPacket; Server.this.pingManager.checkTimeouts(); long keepAliveTime = System.currentTimeMillis(); if (keepAliveTime - this.lastKeepAlive > (long)Voicechat.SERVER_CONFIG.keepAlive.get().intValue()) { Server.this.sendKeepAlives(); this.lastKeepAlive = keepAliveTime; } if ((rawPacket = (RawUdpPacket)Server.this.packetQueue.poll(10L, TimeUnit.MILLISECONDS)) == null) continue; try { message = NetworkMessage.readPacketServer(rawPacket, Server.this); } catch (Exception e) { CooldownTimer.run("failed_reading_packet", () -> Voicechat.LOGGER.warn("Failed to read packet from {}", rawPacket.getSocketAddress())); continue; } if (message == null) continue; if (System.currentTimeMillis() - message.getTimestamp() > message.getTTL()) { CooldownTimer.run("ttl", () -> { Voicechat.LOGGER.error("Dropping voice chat packets! Your Server might be overloaded!", new Object[0]); Voicechat.LOGGER.error("Packet queue has {} packets", Server.this.packetQueue.size()); }); continue; } if (message.getPacket() instanceof AuthenticatePacket) { AuthenticatePacket packet = (AuthenticatePacket)message.getPacket(); Secret secret = (Secret)Server.this.secrets.get(packet.getPlayerUUID()); if (secret != null && secret.equals(packet.getSecret())) { ClientConnection connection = (ClientConnection)Server.this.unCheckedConnections.get(packet.getPlayerUUID()); if (connection == null) { connection = (ClientConnection)Server.this.connections.get(packet.getPlayerUUID()); } if (connection == null) { connection = new ClientConnection(packet.getPlayerUUID(), message.getAddress()); Server.this.unCheckedConnections.put(packet.getPlayerUUID(), connection); Voicechat.LOGGER.info("Successfully authenticated player {}", packet.getPlayerUUID()); } Server.this.sendPacket(new AuthenticateAckPacket(), connection); } } if (message.getPacket() instanceof ConnectionCheckPacket) { ClientConnection connection = Server.this.getUnconnectedSender(message); if (connection == null) { connection = Server.this.getSender(message); if (connection == null) continue; Server.this.sendPacket(new ConnectionCheckAckPacket(), connection); continue; } connection.setLastKeepAliveResponse(System.currentTimeMillis()); Server.this.connections.put(connection.getPlayerUUID(), connection); Server.this.unCheckedConnections.remove(connection.getPlayerUUID()); Voicechat.LOGGER.info("Successfully validated connection of player {}", connection.getPlayerUUID()); Player player = Server.this.server.getPlayer(connection.getPlayerUUID()); if (player != null) { Server.this.playerStateManager.onPlayerVoicechatConnect(player); PluginManager.instance().onPlayerConnected(player); Voicechat.LOGGER.info("Player {} ({}) successfully connected to voice chat", player.getName(), connection.getPlayerUUID()); } Server.this.sendPacket(new ConnectionCheckAckPacket(), connection); continue; } ClientConnection conn = Server.this.getSender(message); if (conn == null) continue; if (message.getPacket() instanceof MicPacket) { Server.this.onMicPacket(conn.getPlayerUUID(), (MicPacket)message.getPacket()); continue; } if (message.getPacket() instanceof PingPacket) { PingPacket packet = (PingPacket)message.getPacket(); Server.this.pingManager.onPongPacket(packet); continue; } if (!(message.getPacket() instanceof KeepAlivePacket)) continue; conn.setLastKeepAliveResponse(System.currentTimeMillis()); } catch (Exception e) { Voicechat.LOGGER.error("Voice chat server error", e); } } } public void close() { this.running = false; } } }