package net.lax1dude.eaglercraft.sp; import net.minecraft.server.MinecraftServer; import net.minecraft.src.EntityPlayerMP; import net.minecraft.src.Packet250CustomPayload; import java.io.*; import java.util.*; import java.util.function.Predicate; public class VoiceChatPlugin { private static final Map voicePlayers = new HashMap<>(); private static final Map> voiceRequests = new HashMap<>(); private static final Set voicePairs = new HashSet<>(); private static final List iceServers = new ArrayList(); private static final int VOICE_SIGNAL_ALLOWED = 0; private static final int VOICE_SIGNAL_REQUEST = 0; private static final int VOICE_SIGNAL_CONNECT = 1; private static final int VOICE_SIGNAL_DISCONNECT = 2; private static final int VOICE_SIGNAL_ICE = 3; private static final int VOICE_SIGNAL_DESC = 4; private static final int VOICE_SIGNAL_GLOBAL = 5; public static boolean handleMessage(EntityPlayerMP player, Packet250CustomPayload payload) { if ("EAG|Voice".equals(payload.channel) && payload.data.length > 0) { deev: { String user = player.username; byte[] msg = payload.data; try { DataInputStream streamIn = new DataInputStream(new ByteArrayInputStream(msg)); int sig = streamIn.read(); switch (sig) { case VOICE_SIGNAL_CONNECT: if (voicePlayers.containsKey(user)) break deev; // user is already using voice chat // send out packet for player joined voice // notice: everyone on the server can see this packet!! however, it doesn't do // anything but let clients know that the player has turned on voice chat ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); dos.write(VOICE_SIGNAL_CONNECT); dos.writeUTF(user); byte[] out = baos.toByteArray(); for (EntityPlayerMP conn : voicePlayers.values()) conn.playerNetServerHandler.sendPacket(new Packet250CustomPayload("EAG|Voice", out)); voicePlayers.put(user, player); for (String username : voicePlayers.keySet()) sendVoicePlayers(username); break; case VOICE_SIGNAL_DISCONNECT: if (!voicePlayers.containsKey(user)) break deev; // user is not using voice chat try { String user2 = streamIn.readUTF(); if (!voicePlayers.containsKey(user2)) break deev; if (removeIf(voicePairs, pair -> (pair[0].equals(user) && pair[1].equals(user2)) || (pair[0].equals(user2) && pair[1].equals(user)))) { ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); DataOutputStream dos2 = new DataOutputStream(baos2); dos2.write(VOICE_SIGNAL_DISCONNECT); dos2.writeUTF(user); voicePlayers.get(user2).playerNetServerHandler.sendPacket( new Packet250CustomPayload("EAG|Voice", baos2.toByteArray())); baos2 = new ByteArrayOutputStream(); dos2 = new DataOutputStream(baos2); dos2.write(VOICE_SIGNAL_DISCONNECT); dos2.writeUTF(user2); player.playerNetServerHandler.sendPacket(new Packet250CustomPayload("EAG|Voice", baos2.toByteArray())); } } catch (EOFException e) { removeUser(user); } break; case VOICE_SIGNAL_REQUEST: if (!voicePlayers.containsKey(user)) break deev; // user is not using voice chat String targetUser = streamIn.readUTF(); if (user.equals(targetUser)) break deev; // prevent duplicates if (checkVoicePair(user, targetUser)) break deev; // already paired if (!voicePlayers.containsKey(targetUser)) break deev; // target user is not using voice chat if (!voiceRequests.containsKey(user)) voiceRequests.put(user, new ExpiringSet<>(2000)); if (voiceRequests.get(user).contains(targetUser)) break deev; voiceRequests.get(user).add(targetUser); // check if other has requested earlier if (voiceRequests.containsKey(targetUser) && voiceRequests.get(targetUser).contains(user)) { if (voiceRequests.containsKey(targetUser)) { voiceRequests.get(targetUser).remove(user); if (voiceRequests.get(targetUser).isEmpty()) voiceRequests.remove(targetUser); } if (voiceRequests.containsKey(user)) { voiceRequests.get(user).remove(targetUser); if (voiceRequests.get(user).isEmpty()) voiceRequests.remove(user); } // send each other add data voicePairs.add(new String[]{user, targetUser}); ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); DataOutputStream dos2 = new DataOutputStream(baos2); dos2.write(VOICE_SIGNAL_CONNECT); dos2.writeUTF(user); dos2.writeBoolean(false); voicePlayers.get(targetUser).playerNetServerHandler.sendPacket( new Packet250CustomPayload("EAG|Voice", baos2.toByteArray())); baos2 = new ByteArrayOutputStream(); dos2 = new DataOutputStream(baos2); dos2.write(VOICE_SIGNAL_CONNECT); dos2.writeUTF(targetUser); dos2.writeBoolean(true); player.playerNetServerHandler.sendPacket(new Packet250CustomPayload("EAG|Voice", baos2.toByteArray())); } break; case VOICE_SIGNAL_ICE: case VOICE_SIGNAL_DESC: if (!voicePlayers.containsKey(user)) break deev; // user is not using voice chat String targetUser2 = streamIn.readUTF(); if (checkVoicePair(user, targetUser2)) { String data = streamIn.readUTF(); ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); DataOutputStream dos2 = new DataOutputStream(baos2); dos2.write(sig); dos2.writeUTF(user); dos2.writeUTF(data); voicePlayers.get(targetUser2).playerNetServerHandler.sendPacket(new Packet250CustomPayload("EAG|Voice", baos2.toByteArray())); } break; default: break; } } catch (Throwable t) { // hacker // t.printStackTrace(); // todo: remove in production removeUser(user); } } return true; } return false; } public static void activate(List ice) { if (iceServers.size() == 0) { iceServers.addAll(ice); for (Object o : MinecraftServer.getServer().getConfigurationManager().playerEntityList) { handleConnect((EntityPlayerMP) o); } } } public static void handleConnect(EntityPlayerMP player) { if (iceServers.size() > 0) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); dos.write(VOICE_SIGNAL_ALLOWED); dos.writeBoolean(true); dos.write(iceServers.size()); for (String str : iceServers) { dos.writeUTF(str); } player.playerNetServerHandler.sendPacket(new Packet250CustomPayload("EAG|Voice", baos.toByteArray())); sendVoicePlayers(player.username); } catch (IOException ignored) { } } } public static void handleDisconnect(EntityPlayerMP player) { removeUser(player.username); } private static void removeUser(String name) { voicePlayers.remove(name); for (String username : voicePlayers.keySet()) { if (!name.equals(username)) sendVoicePlayers(username); } for (String[] voicePair : voicePairs) { String target = null; if (voicePair[0].equals(name)) { target = voicePair[1]; } else if (voicePair[1].equals(name)) { target = voicePair[0]; } if (target != null && voicePlayers.containsKey(target)) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); dos.write(VOICE_SIGNAL_DISCONNECT); dos.writeUTF(name); voicePlayers.get(target).playerNetServerHandler.sendPacket(new Packet250CustomPayload("EAG|Voice", baos.toByteArray())); } catch (IOException ignored) { } } } removeIf(voicePairs, pair -> pair[0].equals(name) || pair[1].equals(name)); } public static void reset() { voicePlayers.clear(); voiceRequests.clear(); voicePairs.clear(); iceServers.clear(); } private static void sendVoicePlayers(String name) { if (!voicePlayers.containsKey(name)) return; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); dos.write(VOICE_SIGNAL_GLOBAL); Set mostlyGlobalPlayers = new HashSet<>(); for (String username : voicePlayers.keySet()) { if (username.equals(name)) continue; if (anyMatch(voicePairs, pair -> (pair[0].equals(name) && pair[1].equals(username)) || (pair[0].equals(username) && pair[1].equals(name)))) continue; mostlyGlobalPlayers.add(username); } if (mostlyGlobalPlayers.size() > 0) { dos.writeInt(mostlyGlobalPlayers.size()); for (String username : mostlyGlobalPlayers) dos.writeUTF(username); voicePlayers.get(name).playerNetServerHandler.sendPacket(new Packet250CustomPayload("EAG|Voice", baos.toByteArray())); } } catch (IOException ignored) { } } private static boolean checkVoicePair(String user1, String user2) { return anyMatch(voicePairs, pair -> (pair[0].equals(user1) && pair[1].equals(user2)) || (pair[0].equals(user2) && pair[1].equals(user1))); } /** * JDK 8 function not available in TeaVM */ private static boolean removeIf(Collection collection, Predicate pre) { boolean ret = false; Iterator itr = collection.iterator(); while (itr.hasNext()) { if (pre.test(itr.next())) { itr.remove(); ret = true; } } return ret; } /** * JDK 8 function not available in TeaVM */ private static boolean anyMatch(Collection collection, Predicate pre) { boolean ret = false; Iterator itr = collection.iterator(); while (itr.hasNext()) { if (pre.test(itr.next())) { return true; } } return false; } }