first commit
Build AsminVoicechat / prepare-release (push) Successful in 14s
Build AsminVoicechat / build-and-test (17) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (18) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (19) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (20) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (21) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (22) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (23) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (24) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (25) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (26) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (27-ea) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (16) (push) Canceled after 38s

This commit is contained in:
asminme committed 2026-10-24 15:18:03 +02:00
commit bbccd4a815
454 files changed
+45050

No files matched your search

@@ -0,0 +1,65 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.common;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
public class AudioUtils {
public static final int MAX_OPUS_PAYLOAD_SIZE = 1275;
public static short[] bytesToShorts(byte[] bytes) {
if (bytes.length % 2 != 0) {
throw new IllegalArgumentException("Input bytes need to be divisible by 2");
}
ShortBuffer sb = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
short[] out = new short[sb.remaining()];
sb.get(out);
return out;
}
public static byte[] shortsToBytes(short[] shorts) {
ByteBuffer bb = ByteBuffer.allocate(shorts.length * 2).order(ByteOrder.LITTLE_ENDIAN);
for (short s : shorts) {
bb.putShort(s);
}
return bb.array();
}
public static short[] floatsToShorts(float[] floats) {
short[] shorts = new short[floats.length];
for (int i = 0; i < floats.length; ++i) {
shorts[i] = Float.valueOf(floats[i]).shortValue();
}
return shorts;
}
public static float[] shortsToFloats(short[] shorts) {
float[] floats = new float[shorts.length];
for (int i = 0; i < shorts.length; ++i) {
floats[i] = Short.valueOf(shorts[i]).floatValue();
}
return floats;
}
public static byte[] floatsToBytes(float[] floats) {
byte[] bytes = new byte[floats.length * 2];
for (int i = 0; i < floats.length; ++i) {
short x = Float.valueOf(floats[i]).shortValue();
bytes[i * 2] = (byte)(x & 0xFF);
bytes[i * 2 + 1] = (byte)((x & 0xFF00) >> 8);
}
return bytes;
}
public static float[] bytesToFloats(byte[] bytes) {
float[] floats = new float[bytes.length / 2];
for (int i = 0; i < bytes.length / 2; ++i) {
floats[i] = (bytes[i * 2 + 1] & 0x80) != 0 ? (float)(Short.MIN_VALUE + ((bytes[i * 2 + 1] & 0x7F) << 8) | bytes[i * 2] & 0xFF) : (float)(bytes[i * 2 + 1] << 8 & 0xFF00 | bytes[i * 2] & 0xFF);
}
return floats;
}
}
@@ -0,0 +1,20 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import de.maxhenkel.voicechat.voice.common.Packet;
public class AuthenticateAckPacket
implements Packet<AuthenticateAckPacket> {
@Override
public AuthenticateAckPacket fromBytes(FriendlyByteBuf buf) {
return new AuthenticateAckPacket();
}
@Override
public void toBytes(FriendlyByteBuf buf) {
}
}
@@ -0,0 +1,46 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import de.maxhenkel.voicechat.voice.common.Packet;
import de.maxhenkel.voicechat.voice.common.Secret;
import java.util.UUID;
public class AuthenticatePacket
implements Packet<AuthenticatePacket> {
private UUID playerUUID;
private Secret secret;
public AuthenticatePacket(UUID playerUUID, Secret secret) {
this.playerUUID = playerUUID;
this.secret = secret;
}
public AuthenticatePacket() {
}
public UUID getPlayerUUID() {
return this.playerUUID;
}
public Secret getSecret() {
return this.secret;
}
@Override
public AuthenticatePacket fromBytes(FriendlyByteBuf buf) {
AuthenticatePacket packet = new AuthenticatePacket();
packet.playerUUID = buf.readUUID();
packet.secret = Secret.fromBytes(buf);
return packet;
}
@Override
public void toBytes(FriendlyByteBuf buf) {
buf.writeUUID(this.playerUUID);
this.secret.toBytes(buf);
}
}
@@ -0,0 +1,77 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.plugins.impl.GroupImpl;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import java.util.Objects;
import java.util.UUID;
public class ClientGroup {
private final UUID id;
private final String name;
private final boolean hasPassword;
private final boolean persistent;
private final boolean hidden;
private final Group.Type type;
public ClientGroup(UUID id, String name, boolean hasPassword, boolean persistent, boolean hidden, Group.Type type) {
this.id = id;
this.name = name;
this.hasPassword = hasPassword;
this.persistent = persistent;
this.hidden = hidden;
this.type = type;
}
public UUID getId() {
return this.id;
}
public String getName() {
return this.name;
}
public boolean hasPassword() {
return this.hasPassword;
}
public boolean isPersistent() {
return this.persistent;
}
public boolean isHidden() {
return this.hidden;
}
public Group.Type getType() {
return this.type;
}
public static ClientGroup fromBytes(FriendlyByteBuf buf) {
return new ClientGroup(buf.readUUID(), buf.readUtf(512), buf.readBoolean(), buf.readBoolean(), buf.readBoolean(), GroupImpl.TypeImpl.fromInt(buf.readShort()));
}
public void toBytes(FriendlyByteBuf buf) {
buf.writeUUID(this.id);
buf.writeUtf(this.name, 512);
buf.writeBoolean(this.hasPassword);
buf.writeBoolean(this.persistent);
buf.writeBoolean(this.hidden);
buf.writeShort(GroupImpl.TypeImpl.toInt(this.type));
}
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
ClientGroup group = (ClientGroup)o;
return Objects.equals(this.id, group.id);
}
}
@@ -0,0 +1,20 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import de.maxhenkel.voicechat.voice.common.Packet;
public class ConnectionCheckAckPacket
implements Packet<ConnectionCheckAckPacket> {
@Override
public ConnectionCheckAckPacket fromBytes(FriendlyByteBuf buf) {
return new ConnectionCheckAckPacket();
}
@Override
public void toBytes(FriendlyByteBuf buf) {
}
}
@@ -0,0 +1,20 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import de.maxhenkel.voicechat.voice.common.Packet;
public class ConnectionCheckPacket
implements Packet<ConnectionCheckPacket> {
@Override
public ConnectionCheckPacket fromBytes(FriendlyByteBuf buf) {
return new ConnectionCheckPacket();
}
@Override
public void toBytes(FriendlyByteBuf buf) {
}
}
@@ -0,0 +1,53 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* javax.annotation.Nullable
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import de.maxhenkel.voicechat.voice.common.SoundPacket;
import java.util.UUID;
import javax.annotation.Nullable;
public class GroupSoundPacket
extends SoundPacket<GroupSoundPacket> {
public GroupSoundPacket(UUID channelId, UUID sender, byte[] data, long sequenceNumber, @Nullable String category) {
super(channelId, sender, data, sequenceNumber, category);
}
public GroupSoundPacket() {
}
@Override
public GroupSoundPacket fromBytes(FriendlyByteBuf buf) {
GroupSoundPacket soundPacket = new GroupSoundPacket();
soundPacket.channelId = buf.readUUID();
soundPacket.sender = buf.readUUID();
soundPacket.data = buf.readByteArray(1275);
soundPacket.sequenceNumber = buf.readLong();
byte data = buf.readByte();
if (this.hasFlag(data, (byte)2)) {
soundPacket.category = buf.readUtf(16);
}
return soundPacket;
}
@Override
public void toBytes(FriendlyByteBuf buf) {
buf.writeUUID(this.channelId);
buf.writeUUID(this.sender);
buf.writeByteArray(this.data);
buf.writeLong(this.sequenceNumber);
byte data = 0;
if (this.category != null) {
data = this.setFlag(data, (byte)2);
}
buf.writeByte(data);
if (this.category != null) {
buf.writeUtf(this.category, 16);
}
}
}
@@ -0,0 +1,20 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import de.maxhenkel.voicechat.voice.common.Packet;
public class KeepAlivePacket
implements Packet<KeepAlivePacket> {
@Override
public KeepAlivePacket fromBytes(FriendlyByteBuf buf) {
return new KeepAlivePacket();
}
@Override
public void toBytes(FriendlyByteBuf buf) {
}
}
@@ -0,0 +1,74 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* javax.annotation.Nullable
* org.bukkit.Location
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import de.maxhenkel.voicechat.voice.common.SoundPacket;
import java.util.UUID;
import javax.annotation.Nullable;
import org.bukkit.Location;
public class LocationSoundPacket
extends SoundPacket<LocationSoundPacket> {
protected Location location;
protected float distance;
public LocationSoundPacket(UUID channelId, UUID sender, Location location, byte[] data, long sequenceNumber, float distance, @Nullable String category) {
super(channelId, sender, data, sequenceNumber, category);
this.location = location;
this.distance = distance;
}
public LocationSoundPacket() {
}
public Location getLocation() {
return this.location;
}
public float getDistance() {
return this.distance;
}
@Override
public LocationSoundPacket fromBytes(FriendlyByteBuf buf) {
LocationSoundPacket soundPacket = new LocationSoundPacket();
soundPacket.channelId = buf.readUUID();
soundPacket.sender = buf.readUUID();
soundPacket.location = new Location(null, buf.readDouble(), buf.readDouble(), buf.readDouble());
soundPacket.data = buf.readByteArray(1275);
soundPacket.sequenceNumber = buf.readLong();
soundPacket.distance = buf.readFloat();
byte data = buf.readByte();
if (this.hasFlag(data, (byte)2)) {
soundPacket.category = buf.readUtf(16);
}
return soundPacket;
}
@Override
public void toBytes(FriendlyByteBuf buf) {
buf.writeUUID(this.channelId);
buf.writeUUID(this.sender);
buf.writeDouble(this.location.getX());
buf.writeDouble(this.location.getY());
buf.writeDouble(this.location.getZ());
buf.writeByteArray(this.data);
buf.writeLong(this.sequenceNumber);
buf.writeFloat(this.distance);
byte data = 0;
if (this.category != null) {
data = this.setFlag(data, (byte)2);
}
buf.writeByte(data);
if (this.category != null) {
buf.writeUtf(this.category, 16);
}
}
}
@@ -0,0 +1,61 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import de.maxhenkel.voicechat.voice.common.Packet;
public class MicPacket
implements Packet<MicPacket> {
private byte[] data;
private boolean whispering;
private long sequenceNumber;
public MicPacket(byte[] data, boolean whispering, long sequenceNumber) {
this.data = data;
this.whispering = whispering;
this.sequenceNumber = sequenceNumber;
}
public MicPacket() {
}
@Override
public long getTTL() {
return 500L;
}
public byte[] getData() {
return this.data;
}
public void setData(byte[] data) {
this.data = data;
}
public long getSequenceNumber() {
return this.sequenceNumber;
}
public boolean isWhispering() {
return this.whispering;
}
@Override
public MicPacket fromBytes(FriendlyByteBuf buf) {
MicPacket soundPacket = new MicPacket();
soundPacket.data = buf.readByteArray(1275);
soundPacket.sequenceNumber = buf.readLong();
soundPacket.whispering = buf.readBoolean();
return soundPacket;
}
@Override
public void toBytes(FriendlyByteBuf buf) {
buf.writeByteArray(this.data);
buf.writeLong(this.sequenceNumber);
buf.writeBoolean(this.whispering);
}
}
@@ -0,0 +1,175 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* io.netty.buffer.Unpooled
* io.netty.handler.codec.DecoderException
* javax.annotation.Nonnull
* javax.annotation.Nullable
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.Voicechat;
import de.maxhenkel.voicechat.api.RawUdpPacket;
import de.maxhenkel.voicechat.debug.PingHandler;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
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.Packet;
import de.maxhenkel.voicechat.voice.common.PingPacket;
import de.maxhenkel.voicechat.voice.common.PlayerSoundPacket;
import de.maxhenkel.voicechat.voice.common.Secret;
import de.maxhenkel.voicechat.voice.server.ClientConnection;
import de.maxhenkel.voicechat.voice.server.Server;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.DecoderException;
import java.lang.reflect.InvocationTargetException;
import java.net.SocketAddress;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
public class NetworkMessage {
public static final byte MAGIC_BYTE = -1;
private final long timestamp;
private Packet<? extends Packet> packet;
private SocketAddress address;
private static final Map<Byte, Class<? extends Packet>> packetRegistry = new HashMap<Byte, Class<? extends Packet>>();
public NetworkMessage(long timestamp, Packet<?> packet) {
this(timestamp);
this.packet = packet;
}
public NetworkMessage(Packet<?> packet) {
this(System.currentTimeMillis());
this.packet = packet;
}
private NetworkMessage(long timestamp) {
this.timestamp = timestamp;
}
@Nonnull
public Packet<? extends Packet> getPacket() {
return this.packet;
}
public long getTimestamp() {
return this.timestamp;
}
public long getTTL() {
return this.packet.getTTL();
}
public SocketAddress getAddress() {
return this.address;
}
@Nullable
public static NetworkMessage readPacketServer(RawUdpPacket packet, Server server) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
try {
byte[] data = packet.getData();
FriendlyByteBuf b = new FriendlyByteBuf(Unpooled.wrappedBuffer((byte[])data));
if (b.readByte() != -1) {
Voicechat.LOGGER.debug("Received invalid packet from {}", packet.getSocketAddress());
return null;
}
UUID playerID = b.readUUID();
if (!server.hasSecret(playerID)) {
if (PingHandler.onPacket(server, packet.getSocketAddress(), playerID, b)) {
return null;
}
Voicechat.LOGGER.debug("Player {} does not have a secret", playerID);
return null;
}
return NetworkMessage.readFromBytes(packet.getSocketAddress(), server.getSecret(playerID), b.readByteArray(2048), packet.getTimestamp());
}
catch (DecoderException | IndexOutOfBoundsException e) {
Voicechat.LOGGER.debug("Received invalid packet from {}", packet.getSocketAddress());
return null;
}
}
@Nullable
private static NetworkMessage readFromBytes(SocketAddress socketAddress, Secret secret, byte[] encryptedPayload, long timestamp) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
byte[] decrypt;
try {
decrypt = secret.decrypt(encryptedPayload);
}
catch (Exception e) {
Voicechat.LOGGER.debug("Failed to decrypt packet from {}", socketAddress);
return null;
}
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.wrappedBuffer((byte[])decrypt));
byte packetType = buffer.readByte();
Class<? extends Packet> packetClass = packetRegistry.get(packetType);
if (packetClass == null) {
Voicechat.LOGGER.debug("Got invalid packet ID {}", packetType);
return null;
}
Packet p = packetClass.getDeclaredConstructor(new Class[0]).newInstance(new Object[0]);
NetworkMessage message = new NetworkMessage(timestamp);
message.address = socketAddress;
message.packet = p.fromBytes(buffer);
return message;
}
private static byte getPacketType(Packet<? extends Packet> packet) {
for (Map.Entry<Byte, Class<? extends Packet>> entry : packetRegistry.entrySet()) {
if (!packet.getClass().equals(entry.getValue())) continue;
return entry.getKey();
}
return -1;
}
public byte[] writeServer(Server server, ClientConnection connection) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
byte[] payload = this.write(server.getSecret(connection.getPlayerUUID()));
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer((int)(1 + payload.length)));
buffer.writeByte(-1);
buffer.writeByteArray(payload);
byte[] bytes = new byte[buffer.readableBytes()];
buffer.readBytes(bytes);
return bytes;
}
public byte[] write(Secret secret) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer());
byte type = NetworkMessage.getPacketType(this.packet);
if (type < 0) {
throw new IllegalArgumentException("Packet type not found");
}
buffer.writeByte(type);
this.packet.toBytes(buffer);
return secret.encrypt(buffer.array());
}
static {
packetRegistry.put((byte)1, MicPacket.class);
packetRegistry.put((byte)2, PlayerSoundPacket.class);
packetRegistry.put((byte)3, GroupSoundPacket.class);
packetRegistry.put((byte)4, LocationSoundPacket.class);
packetRegistry.put((byte)5, AuthenticatePacket.class);
packetRegistry.put((byte)6, AuthenticateAckPacket.class);
packetRegistry.put((byte)7, PingPacket.class);
packetRegistry.put((byte)8, KeepAlivePacket.class);
packetRegistry.put((byte)9, ConnectionCheckPacket.class);
packetRegistry.put((byte)10, ConnectionCheckAckPacket.class);
}
}
@@ -0,0 +1,17 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
public interface Packet<T extends Packet> {
public T fromBytes(FriendlyByteBuf var1);
public void toBytes(FriendlyByteBuf var1);
default public long getTTL() {
return 10000L;
}
}
@@ -0,0 +1,45 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import de.maxhenkel.voicechat.voice.common.Packet;
import java.util.UUID;
public class PingPacket
implements Packet<PingPacket> {
private UUID id;
private long timestamp;
public PingPacket(UUID id, long timestamp) {
this.id = id;
this.timestamp = timestamp;
}
public PingPacket() {
}
public long getTimestamp() {
return this.timestamp;
}
public UUID getId() {
return this.id;
}
@Override
public PingPacket fromBytes(FriendlyByteBuf buf) {
PingPacket soundPacket = new PingPacket();
soundPacket.id = buf.readUUID();
soundPacket.timestamp = buf.readLong();
return soundPacket;
}
@Override
public void toBytes(FriendlyByteBuf buf) {
buf.writeUUID(this.id);
buf.writeLong(this.timestamp);
}
}
@@ -0,0 +1,77 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* javax.annotation.Nullable
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import de.maxhenkel.voicechat.voice.common.SoundPacket;
import java.util.UUID;
import javax.annotation.Nullable;
public class PlayerSoundPacket
extends SoundPacket<PlayerSoundPacket> {
protected boolean whispering;
protected float distance;
public PlayerSoundPacket(UUID channelId, UUID sender, byte[] data, long sequenceNumber, boolean whispering, float distance, @Nullable String category) {
super(channelId, sender, data, sequenceNumber, category);
this.whispering = whispering;
this.distance = distance;
}
public PlayerSoundPacket() {
}
@Override
public UUID getSender() {
return this.sender;
}
public boolean isWhispering() {
return this.whispering;
}
public float getDistance() {
return this.distance;
}
@Override
public PlayerSoundPacket fromBytes(FriendlyByteBuf buf) {
PlayerSoundPacket soundPacket = new PlayerSoundPacket();
soundPacket.channelId = buf.readUUID();
soundPacket.sender = buf.readUUID();
soundPacket.data = buf.readByteArray(1275);
soundPacket.sequenceNumber = buf.readLong();
soundPacket.distance = buf.readFloat();
byte data = buf.readByte();
soundPacket.whispering = this.hasFlag(data, (byte)1);
if (this.hasFlag(data, (byte)2)) {
soundPacket.category = buf.readUtf(16);
}
return soundPacket;
}
@Override
public void toBytes(FriendlyByteBuf buf) {
buf.writeUUID(this.channelId);
buf.writeUUID(this.sender);
buf.writeByteArray(this.data);
buf.writeLong(this.sequenceNumber);
buf.writeFloat(this.distance);
byte data = 0;
if (this.whispering) {
data = this.setFlag(data, (byte)1);
}
if (this.category != null) {
data = this.setFlag(data, (byte)2);
}
buf.writeByte(data);
if (this.category != null) {
buf.writeUtf(this.category, 16);
}
}
}
@@ -0,0 +1,100 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* javax.annotation.Nullable
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.util.FriendlyByteBuf;
import java.util.UUID;
import javax.annotation.Nullable;
public class PlayerState {
private UUID uuid;
private String name;
private boolean disabled;
private boolean disconnected;
@Nullable
private UUID group;
public PlayerState(UUID uuid, String name, boolean disabled, boolean disconnected) {
this.uuid = uuid;
this.name = name;
this.disabled = disabled;
this.disconnected = disconnected;
}
public UUID getUuid() {
return this.uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public boolean isDisabled() {
return this.disabled;
}
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
public boolean isDisconnected() {
return this.disconnected;
}
public void setDisconnected(boolean disconnected) {
this.disconnected = disconnected;
}
@Nullable
public UUID getGroup() {
return this.group;
}
public void setGroup(@Nullable UUID group) {
this.group = group;
}
public boolean hasGroup() {
return this.group != null;
}
public String toString() {
return "{disabled=" + this.disabled + ", disconnected=" + this.disconnected + ", uuid=" + this.uuid + ", name=" + this.name + ", group=" + this.group + '}';
}
public static PlayerState fromBytes(FriendlyByteBuf buf) {
boolean disabled = buf.readBoolean();
boolean disconnected = buf.readBoolean();
UUID uuid = buf.readUUID();
String name = buf.readUtf(Short.MAX_VALUE);
PlayerState state = new PlayerState(uuid, name, disabled, disconnected);
if (buf.readBoolean()) {
state.setGroup(buf.readUUID());
}
return state;
}
public void toBytes(FriendlyByteBuf buf) {
buf.writeBoolean(this.disabled);
buf.writeBoolean(this.disconnected);
buf.writeUUID(this.uuid);
buf.writeUtf(this.name);
buf.writeBoolean(this.hasGroup());
if (this.hasGroup()) {
buf.writeUUID(this.group);
}
}
}
@@ -0,0 +1,140 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* io.netty.buffer.ByteBuf
*/
package de.maxhenkel.voicechat.voice.common;
import io.netty.buffer.ByteBuf;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Secret {
public static final int SECRET_SIZE_BYTES = 16;
public static final int IV_SIZE_BYTES = 12;
public static final int TAG_LEN_BITS = 128;
public static final String CIPHER = "AES/GCM/NoPadding";
private static final SecureRandom RANDOM = new SecureRandom();
private final byte[] secret;
private final SecretKeySpec keySpec;
private volatile boolean legacyProtocol;
protected Secret(byte[] byArray) {
if (byArray == null || byArray.length != 16) {
throw new IllegalArgumentException("Voice chat secret must contain exactly 16 bytes");
}
this.secret = (byte[])byArray.clone();
this.keySpec = new SecretKeySpec(this.secret, "AES");
}
public static Secret generateNewRandomSecret() {
byte[] byArray = new byte[16];
RANDOM.nextBytes(byArray);
return new Secret(byArray);
}
public static Secret fromBytes(byte[] byArray) {
return new Secret(byArray);
}
public static Secret fromBytes(ByteBuf byteBuf) {
byte[] byArray = new byte[16];
byteBuf.readBytes(byArray);
return Secret.fromBytes(byArray);
}
public void toBytes(ByteBuf byteBuf) {
byteBuf.writeBytes(this.secret);
}
public byte[] getSecret() {
return (byte[])this.secret.clone();
}
public SecretKeySpec getKeySpec() {
return this.keySpec;
}
public void setLegacyProtocol(boolean bl) {
this.legacyProtocol = bl;
}
public boolean isLegacyProtocol() {
return this.legacyProtocol;
}
public static byte[] generateIV() {
byte[] byArray = new byte[12];
RANDOM.nextBytes(byArray);
return byArray;
}
private static byte[] generateLegacyIV() {
byte[] byArray = new byte[16];
RANDOM.nextBytes(byArray);
return byArray;
}
public byte[] encrypt(byte[] byArray) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
if (this.legacyProtocol) {
byte[] byArray2 = Secret.generateLegacyIV();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(1, (Key)this.keySpec, new IvParameterSpec(byArray2));
byte[] byArray3 = cipher.doFinal(byArray);
byte[] byArray4 = new byte[byArray2.length + byArray3.length];
System.arraycopy(byArray2, 0, byArray4, 0, byArray2.length);
System.arraycopy(byArray3, 0, byArray4, byArray2.length, byArray3.length);
return byArray4;
}
byte[] byArray5 = Secret.generateIV();
Cipher cipher = Cipher.getInstance(CIPHER);
cipher.init(1, (Key)this.keySpec, new GCMParameterSpec(128, byArray5));
byte[] byArray6 = cipher.doFinal(byArray);
byte[] byArray7 = new byte[byArray5.length + byArray6.length];
System.arraycopy(byArray5, 0, byArray7, 0, byArray5.length);
System.arraycopy(byArray6, 0, byArray7, byArray5.length, byArray6.length);
return byArray7;
}
public byte[] decrypt(byte[] byArray) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
if (this.legacyProtocol) {
if (byArray.length < 32 || (byArray.length - 16) % 16 != 0) {
throw new IllegalBlockSizeException("Invalid legacy AES-CBC packet size");
}
byte[] byArray2 = Arrays.copyOfRange(byArray, 0, 16);
byte[] byArray3 = Arrays.copyOfRange(byArray, 16, byArray.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(2, (Key)this.keySpec, new IvParameterSpec(byArray2));
return cipher.doFinal(byArray3);
}
if (byArray.length < 28) {
throw new IllegalBlockSizeException("Invalid AES-GCM packet size");
}
byte[] byArray4 = Arrays.copyOfRange(byArray, 0, 12);
byte[] byArray5 = Arrays.copyOfRange(byArray, 12, byArray.length);
Cipher cipher = Cipher.getInstance(CIPHER);
cipher.init(2, (Key)this.keySpec, new GCMParameterSpec(128, byArray4));
return cipher.doFinal(byArray5);
}
public boolean equals(Object object) {
return object instanceof Secret && Arrays.equals(this.secret, ((Secret)object).secret);
}
public int hashCode() {
return Arrays.hashCode(this.secret);
}
}
@@ -0,0 +1,64 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* javax.annotation.Nullable
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.voice.common.Packet;
import java.util.UUID;
import javax.annotation.Nullable;
public abstract class SoundPacket<T extends SoundPacket>
implements Packet<T> {
public static final byte WHISPER_MASK = 1;
public static final byte HAS_CATEGORY_MASK = 2;
protected UUID channelId;
protected UUID sender;
protected byte[] data;
protected long sequenceNumber;
@Nullable
protected String category;
public SoundPacket(UUID channelId, UUID sender, byte[] data, long sequenceNumber, @Nullable String category) {
this.channelId = channelId;
this.sender = sender;
this.data = data;
this.sequenceNumber = sequenceNumber;
this.category = category;
}
public SoundPacket() {
}
public UUID getChannelId() {
return this.channelId;
}
public UUID getSender() {
return this.sender;
}
public byte[] getData() {
return this.data;
}
public long getSequenceNumber() {
return this.sequenceNumber;
}
@Nullable
public String getCategory() {
return this.category;
}
protected boolean hasFlag(byte data, byte mask) {
return (data & mask) != 0;
}
protected byte setFlag(byte data, byte mask) {
return (byte)(data | mask);
}
}
@@ -0,0 +1,15 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.common;
import de.maxhenkel.voicechat.Voicechat;
public class Utils {
public static final int MAX_VOICE_CHAT_PACKET_SIZE = 2048;
public static float getDefaultDistance() {
return Voicechat.SERVER_CONFIG.voiceChatDistance.get().floatValue();
}
}
@@ -0,0 +1,42 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.server;
import de.maxhenkel.voicechat.voice.common.NetworkMessage;
import de.maxhenkel.voicechat.voice.server.Server;
import java.net.SocketAddress;
import java.util.UUID;
public class ClientConnection {
private UUID playerUUID;
private SocketAddress address;
private long lastKeepAliveResponse;
public ClientConnection(UUID playerUUID, SocketAddress address) {
this.playerUUID = playerUUID;
this.address = address;
this.lastKeepAliveResponse = System.currentTimeMillis();
}
public UUID getPlayerUUID() {
return this.playerUUID;
}
public SocketAddress getAddress() {
return this.address;
}
public long getLastKeepAliveResponse() {
return this.lastKeepAliveResponse;
}
public void setLastKeepAliveResponse(long lastKeepAliveResponse) {
this.lastKeepAliveResponse = lastKeepAliveResponse;
}
public void send(Server server, NetworkMessage message) throws Exception {
server.getSocket().send(message.writeServer(server, this), this.address);
}
}
@@ -0,0 +1,88 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* javax.annotation.Nullable
*/
package de.maxhenkel.voicechat.voice.server;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.voice.common.ClientGroup;
import java.util.UUID;
import javax.annotation.Nullable;
public class Group {
private UUID id;
private String name;
@Nullable
private String password;
private boolean persistent;
private boolean hidden;
private Group.Type type;
public Group(UUID id, String name, @Nullable String password, boolean persistent, boolean hidden, Group.Type type) {
this.id = id;
this.name = name;
this.password = password;
this.persistent = persistent;
this.hidden = hidden;
this.type = type;
}
public Group(UUID id, String name, @Nullable String password, boolean persistent) {
this(id, name, password, persistent, false, Group.Type.NORMAL);
}
public Group(UUID id, String name, @Nullable String password) {
this(id, name, password, false);
}
public Group(UUID id, String name) {
this(id, name, null);
}
public Group() {
}
public UUID getId() {
return this.id;
}
public String getName() {
return this.name;
}
@Nullable
public String getPassword() {
return this.password;
}
public boolean isPersistent() {
return this.persistent;
}
public boolean isHidden() {
return this.hidden;
}
public Group.Type getType() {
return this.type;
}
public boolean isOpen() {
return this.type == Group.Type.OPEN;
}
public boolean isNormal() {
return this.type == Group.Type.NORMAL;
}
public boolean isIsolated() {
return this.type == Group.Type.ISOLATED;
}
public ClientGroup toClientGroup() {
return new ClientGroup(this.id, this.name, this.password != null, this.persistent, this.hidden, this.type);
}
}
@@ -0,0 +1,100 @@
/*
* Decompiled with CFR 0.152.
*/
package de.maxhenkel.voicechat.voice.server;
import de.maxhenkel.voicechat.Voicechat;
import de.maxhenkel.voicechat.voice.common.PingPacket;
import de.maxhenkel.voicechat.voice.server.ClientConnection;
import de.maxhenkel.voicechat.voice.server.Server;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class PingManager {
private final Map<UUID, Ping> listeners;
private final Server server;
public PingManager(Server server) {
this.server = server;
this.listeners = new ConcurrentHashMap<UUID, Ping>();
}
public void onPongPacket(PingPacket packet) {
Voicechat.LOGGER.info("Received pong {}", packet.getId());
Ping ping = this.listeners.remove(packet.getId());
if (ping == null) {
return;
}
ping.listener.onPong(ping.attempt, System.currentTimeMillis() - packet.getTimestamp());
}
public void checkTimeouts() {
if (this.listeners.isEmpty()) {
return;
}
List timedOut = this.listeners.entrySet().stream().filter(uuidPingEntry -> ((Ping)uuidPingEntry.getValue()).isTimedOut()).collect(Collectors.toList());
for (Map.Entry pingEntry : timedOut) {
Ping ping = (Ping)pingEntry.getValue();
if (ping.attempt >= ping.maxAttempts) {
this.listeners.remove(pingEntry.getKey());
ping.listener.onTimeout(ping.attempt);
continue;
}
ping.listener.onFailedAttempt(ping.attempt);
try {
ping.send();
}
catch (Exception e) {
ping.listener.onTimeout(ping.attempt);
Voicechat.LOGGER.warn("Failed to send ping {} after attempt {}", ping.id, ping.attempt);
}
}
}
public void sendPing(ClientConnection connection, long timeout, int attempts, PingListener listener) throws Exception {
Ping ping = new Ping(connection, listener, timeout, attempts);
this.listeners.put(ping.id, ping);
ping.send();
}
private class Ping {
private final UUID id = UUID.randomUUID();
private final ClientConnection connection;
private final PingListener listener;
private long timestamp;
private final long timeout;
private final int maxAttempts;
private int attempt;
public Ping(ClientConnection connection, PingListener listener, long timeout, int maxAttempts) {
this.connection = connection;
this.listener = listener;
this.timeout = timeout;
this.maxAttempts = maxAttempts;
this.attempt = 0;
}
public boolean isTimedOut() {
return System.currentTimeMillis() - this.timestamp >= this.timeout;
}
public void send() throws Exception {
this.timestamp = System.currentTimeMillis();
++this.attempt;
PingManager.this.server.sendPacketRaw(new PingPacket(this.id, this.timestamp), this.connection);
Voicechat.LOGGER.info("Sent ping {} attempt {}", this.id, this.attempt);
}
}
public static interface PingListener {
public void onPong(int var1, long var2);
public void onFailedAttempt(int var1);
public void onTimeout(int var1);
}
}
@@ -0,0 +1,155 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* javax.annotation.Nullable
* org.bukkit.Bukkit
* org.bukkit.entity.Player
* org.bukkit.event.Listener
* org.bukkit.event.player.PlayerJoinEvent
* org.bukkit.event.player.PlayerQuitEvent
*/
package de.maxhenkel.voicechat.voice.server;
import de.maxhenkel.voicechat.Voicechat;
import de.maxhenkel.voicechat.compatibility.PlayerHideEvent;
import de.maxhenkel.voicechat.compatibility.PlayerShowEvent;
import de.maxhenkel.voicechat.net.NetManager;
import de.maxhenkel.voicechat.net.PlayerStatePacket;
import de.maxhenkel.voicechat.net.PlayerStatesPacket;
import de.maxhenkel.voicechat.net.RemovePlayerStatePacket;
import de.maxhenkel.voicechat.net.UpdateStatePacket;
import de.maxhenkel.voicechat.plugins.PluginManager;
import de.maxhenkel.voicechat.voice.common.PlayerState;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nullable;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class PlayerStateManager
implements Listener {
private final ConcurrentHashMap<UUID, PlayerState> states = new ConcurrentHashMap();
public void onUpdateStatePacket(Player player, UpdateStatePacket packet) {
PlayerState state = this.states.get(player.getUniqueId());
if (state == null) {
state = PlayerStateManager.defaultDisconnectedState(player);
}
state.setDisabled(packet.isDisabled());
this.states.put(player.getUniqueId(), state);
this.broadcastState(player, state);
Voicechat.LOGGER.debug("Got state of {}: {}", player.getName(), state);
}
public void onPlayerQuit(PlayerQuitEvent event) {
this.states.remove(event.getPlayer().getUniqueId());
this.broadcastRemoveState(event.getPlayer());
Voicechat.LOGGER.debug("Removing state of {}", event.getPlayer().getName());
}
public void onPlayerJoin(PlayerJoinEvent event) {
PlayerState state = PlayerStateManager.defaultDisconnectedState(event.getPlayer());
this.states.put(event.getPlayer().getUniqueId(), state);
this.broadcastState(event.getPlayer(), state);
Voicechat.LOGGER.debug("Setting default state of {}: {}", event.getPlayer().getName(), state);
}
public void onPlayerHide(PlayerHideEvent event) {
RemovePlayerStatePacket packet = new RemovePlayerStatePacket(event.getHiddenPlayer().getUniqueId());
NetManager.sendToClient(event.getObserver(), packet);
Voicechat.LOGGER.debug("Removing state of {} for {}", event.getHiddenPlayer().getName(), event.getObserver().getName());
}
public void onPlayerShow(PlayerShowEvent event) {
PlayerState state = this.states.get(event.getShownPlayer().getUniqueId());
if (state == null) {
state = PlayerStateManager.defaultDisconnectedState(event.getShownPlayer());
}
PlayerStatePacket packet = new PlayerStatePacket(state);
NetManager.sendToClient(event.getObserver(), packet);
Voicechat.LOGGER.debug("Sending state of {} to {}", event.getShownPlayer().getName(), event.getObserver().getName());
}
public void broadcastState(@Nullable Player stateOwner, PlayerState state) {
PlayerStatePacket packet = new PlayerStatePacket(state);
for (Player receiver : Voicechat.INSTANCE.getServer().getOnlinePlayers()) {
if (stateOwner != null && !Voicechat.compatibility.canSee(receiver, stateOwner)) continue;
NetManager.sendToClient(receiver, packet);
}
PluginManager.instance().onPlayerStateChanged(state);
}
public void broadcastRemoveState(Player stateOwner) {
RemovePlayerStatePacket packet = new RemovePlayerStatePacket(stateOwner.getUniqueId());
for (Player receiver : Voicechat.INSTANCE.getServer().getOnlinePlayers()) {
NetManager.sendToClient(receiver, packet);
}
PluginManager.instance().onPlayerStateChanged(PlayerStateManager.defaultDisconnectedState(stateOwner));
}
public void onPlayerCompatibilityCheckSucceeded(Player player) {
ArrayList<PlayerState> stateList = new ArrayList<PlayerState>(this.states.size());
for (PlayerState state : this.states.values()) {
Player otherPlayer = Voicechat.INSTANCE.getServer().getPlayer(state.getUuid());
if (otherPlayer == null || !Voicechat.compatibility.canSee(player, otherPlayer)) continue;
stateList.add(state);
}
PlayerStatesPacket packet = new PlayerStatesPacket(stateList);
NetManager.sendToClient(player, packet);
Voicechat.LOGGER.debug("Sending initial states to {}", player.getName());
}
public void onPlayerVoicechatDisconnect(UUID uuid) {
PlayerState state = this.states.get(uuid);
if (state == null) {
return;
}
state.setDisconnected(true);
Player player = Bukkit.getPlayer((UUID)uuid);
this.broadcastState(player, state);
Voicechat.LOGGER.debug("Set state of {} to disconnected: {}", uuid, state);
}
public void onPlayerVoicechatConnect(Player player) {
PlayerState state = this.states.get(player.getUniqueId());
if (state == null) {
state = PlayerStateManager.defaultDisconnectedState(player);
}
state.setDisconnected(false);
this.states.put(player.getUniqueId(), state);
this.broadcastState(player, state);
Voicechat.LOGGER.debug("Set state of {} to connected: {}", player.getName(), state);
}
@Nullable
public PlayerState getState(UUID playerUUID) {
return this.states.get(playerUUID);
}
public static PlayerState defaultDisconnectedState(Player player) {
return new PlayerState(player.getUniqueId(), player.getName(), false, true);
}
public void setGroup(Player player, @Nullable UUID group) {
PlayerState state = this.states.get(player.getUniqueId());
if (state == null) {
state = PlayerStateManager.defaultDisconnectedState(player);
Voicechat.LOGGER.debug("Defaulting to default state for {}: {}", player.getName(), state);
}
state.setGroup(group);
this.states.put(player.getUniqueId(), state);
this.broadcastState(player, state);
Voicechat.LOGGER.debug("Setting group of {}: {}", player.getName(), state);
}
public Collection<PlayerState> getStates() {
return this.states.values();
}
}
@@ -0,0 +1,502 @@
/*
* 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<UUID, ClientConnection> connections;
private final Map<UUID, ClientConnection> unCheckedConnections;
private final Map<UUID, Secret> secrets;
private final int port;
private final org.bukkit.Server server;
private final VoicechatSocket socket;
private final ProcessThread processThread;
private final BlockingQueue<RawUdpPacket> 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<UUID, ClientConnection>();
this.unCheckedConnections = new ConcurrentHashMap<UUID, ClientConnection>();
this.secrets = new ConcurrentHashMap<UUID, Secret>();
this.packetQueue = new LinkedBlockingQueue<RawUdpPacket>();
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<Player> 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<UUID, ClientConnection> 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;
}
}
}
@@ -0,0 +1,54 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* javax.annotation.Nullable
* org.bukkit.entity.Player
*/
package de.maxhenkel.voicechat.voice.server;
import de.maxhenkel.voicechat.Voicechat;
import de.maxhenkel.voicechat.net.AddCategoryPacket;
import de.maxhenkel.voicechat.net.NetManager;
import de.maxhenkel.voicechat.net.RemoveCategoryPacket;
import de.maxhenkel.voicechat.plugins.CategoryManager;
import de.maxhenkel.voicechat.plugins.impl.VolumeCategoryImpl;
import javax.annotation.Nullable;
import org.bukkit.entity.Player;
public class ServerCategoryManager
extends CategoryManager {
public void onPlayerCompatibilityCheckSucceeded(Player player) {
Voicechat.LOGGER.debug("Synchronizing {} volume categories with {}", this.categories.size(), player.getName());
for (VolumeCategoryImpl category : this.getCategories()) {
NetManager.sendToClient(player, new AddCategoryPacket(category));
}
}
@Override
public void addCategory(VolumeCategoryImpl category) {
super.addCategory(category);
Voicechat.LOGGER.debug("Synchronizing volume category {} with all players", category.getId());
this.broadcastAddCategory(category);
}
@Override
@Nullable
public VolumeCategoryImpl removeCategory(String categoryId) {
VolumeCategoryImpl volumeCategory = super.removeCategory(categoryId);
Voicechat.LOGGER.debug("Removing volume category {} for all players", categoryId);
this.broadcastRemoveCategory(categoryId);
return volumeCategory;
}
private void broadcastAddCategory(VolumeCategoryImpl category) {
AddCategoryPacket packet = new AddCategoryPacket(category);
Voicechat.INSTANCE.getServer().getOnlinePlayers().forEach(p -> NetManager.sendToClient(p, packet));
}
private void broadcastRemoveCategory(String categoryId) {
RemoveCategoryPacket packet = new RemoveCategoryPacket(categoryId);
Voicechat.INSTANCE.getServer().getOnlinePlayers().forEach(p -> NetManager.sendToClient(p, packet));
}
}
@@ -0,0 +1,185 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* javax.annotation.Nullable
* org.bukkit.entity.Player
* org.bukkit.event.player.PlayerQuitEvent
*/
package de.maxhenkel.voicechat.voice.server;
import de.maxhenkel.voicechat.Voicechat;
import de.maxhenkel.voicechat.net.AddGroupPacket;
import de.maxhenkel.voicechat.net.CreateGroupPacket;
import de.maxhenkel.voicechat.net.JoinGroupPacket;
import de.maxhenkel.voicechat.net.JoinedGroupPacket;
import de.maxhenkel.voicechat.net.LeaveGroupPacket;
import de.maxhenkel.voicechat.net.NetManager;
import de.maxhenkel.voicechat.net.RemoveGroupPacket;
import de.maxhenkel.voicechat.permission.PermissionManager;
import de.maxhenkel.voicechat.plugins.PluginManager;
import de.maxhenkel.voicechat.voice.common.PlayerState;
import de.maxhenkel.voicechat.voice.server.Group;
import de.maxhenkel.voicechat.voice.server.PlayerStateManager;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerQuitEvent;
public class ServerGroupManager {
private final Map<UUID, Group> groups = new ConcurrentHashMap<UUID, Group>();
public void onJoinGroupPacket(Player player, JoinGroupPacket packet) {
if (!Voicechat.SERVER_CONFIG.groupsEnabled.get().booleanValue()) {
return;
}
if (!player.hasPermission(PermissionManager.GROUPS_PERMISSION)) {
NetManager.sendStatusMessage(player, "message.voicechat.no_group_permission", new String[0]);
return;
}
this.joinGroup(this.groups.get(packet.getGroup()), player, packet.getPassword());
}
public void onCreateGroupPacket(Player player, CreateGroupPacket packet) {
if (!Voicechat.SERVER_CONFIG.groupsEnabled.get().booleanValue()) {
return;
}
if (!player.hasPermission(PermissionManager.GROUPS_PERMISSION)) {
NetManager.sendStatusMessage(player, "message.voicechat.no_group_permission", new String[0]);
return;
}
if (!Voicechat.GROUP_REGEX.matcher(packet.getName()).matches()) {
Voicechat.LOGGER.warn("Player {} tried to create a group with an invalid name", player.getDisplayName());
return;
}
if (packet.getPassword() != null && !Voicechat.GROUP_REGEX.matcher(packet.getPassword()).matches()) {
Voicechat.LOGGER.warn("Player {} tried to create a group with an invalid password", player.getDisplayName());
return;
}
Group group = new Group(UUID.randomUUID(), packet.getName(), packet.getPassword(), false, false, packet.getType());
this.addGroup(group, player);
}
public void onLeaveGroupPacket(Player player, LeaveGroupPacket packet) {
this.leaveGroup(player);
}
public void onPlayerCompatibilityCheckSucceeded(Player player) {
Voicechat.LOGGER.debug("Synchronizing {} groups with {}", this.groups.size(), player.getDisplayName());
for (Group group : this.groups.values()) {
NetManager.sendToClient(player, new AddGroupPacket(group.toClientGroup()));
}
}
public void onPlayerQuit(PlayerQuitEvent event) {
this.cleanupGroups();
}
private PlayerStateManager getStates() {
return Voicechat.SERVER.getServer().getPlayerStateManager();
}
public void addGroup(Group group, @Nullable Player player) {
if (PluginManager.instance().onCreateGroup(player, group)) {
return;
}
this.groups.put(group.getId(), group);
this.broadcastAddGroup(group);
if (player == null) {
return;
}
PlayerStateManager manager = this.getStates();
manager.setGroup(player, group.getId());
NetManager.sendToClient(player, new JoinedGroupPacket(group.getId(), false));
}
public void joinGroup(@Nullable Group group, Player player, String password) {
if (group != null && group.getPassword() != null && !group.getPassword().equals(password)) {
NetManager.sendToClient(player, new JoinedGroupPacket(null, true));
return;
}
if (PluginManager.instance().onJoinGroup(player, group)) {
return;
}
if (group == null) {
NetManager.sendToClient(player, new JoinedGroupPacket(null, false));
return;
}
PlayerStateManager manager = this.getStates();
manager.setGroup(player, group.getId());
NetManager.sendToClient(player, new JoinedGroupPacket(group.getId(), false));
}
public void leaveGroup(Player player) {
if (PluginManager.instance().onLeaveGroup(player)) {
return;
}
PlayerStateManager manager = this.getStates();
manager.setGroup(player, null);
NetManager.sendToClient(player, new JoinedGroupPacket(null, false));
this.cleanupGroups();
}
public void cleanupGroups() {
PlayerStateManager manager = this.getStates();
List usedGroups = manager.getStates().stream().filter(PlayerState::hasGroup).map(PlayerState::getGroup).distinct().collect(Collectors.toList());
List groupsToRemove = this.groups.entrySet().stream().filter(entry -> !((Group)entry.getValue()).isPersistent()).map(Map.Entry::getKey).filter(uuid -> !usedGroups.contains(uuid)).collect(Collectors.toList());
for (UUID uuid2 : groupsToRemove) {
this.removeGroup(uuid2);
}
}
public boolean removeGroup(UUID groupId) {
Group group = this.groups.get(groupId);
if (group == null) {
return false;
}
PlayerStateManager manager = this.getStates();
if (manager.getStates().stream().anyMatch(state -> state.hasGroup() && state.getGroup().equals(groupId))) {
return false;
}
if (PluginManager.instance().onRemoveGroup(group)) {
return false;
}
this.groups.remove(groupId);
this.broadcastRemoveGroup(groupId);
return true;
}
@Nullable
public Group getGroup(UUID groupID) {
return this.groups.get(groupID);
}
private void broadcastAddGroup(Group group) {
AddGroupPacket packet = new AddGroupPacket(group.toClientGroup());
Voicechat.INSTANCE.getServer().getOnlinePlayers().forEach(p -> NetManager.sendToClient(p, packet));
}
private void broadcastRemoveGroup(UUID group) {
RemoveGroupPacket packet = new RemoveGroupPacket(group);
Voicechat.INSTANCE.getServer().getOnlinePlayers().forEach(p -> NetManager.sendToClient(p, packet));
}
@Nullable
public Group getPlayerGroup(Player player) {
PlayerState state = this.getStates().getState(player.getUniqueId());
if (state == null) {
return null;
}
UUID groupId = state.getGroup();
if (groupId == null) {
return null;
}
return this.getGroup(groupId);
}
public Map<UUID, Group> getGroups() {
return this.groups;
}
}
@@ -0,0 +1,107 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* javax.annotation.Nullable
* org.bukkit.Bukkit
* org.bukkit.Location
* org.bukkit.World
* org.bukkit.entity.Player
* org.bukkit.event.EventHandler
* org.bukkit.event.Listener
* org.bukkit.event.player.PlayerJoinEvent
* org.bukkit.event.player.PlayerQuitEvent
* org.bukkit.plugin.Plugin
*/
package de.maxhenkel.voicechat.voice.server;
import de.maxhenkel.voicechat.Voicechat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.Plugin;
public class ServerPlayerManager
implements Listener {
private static final long REFRESH_INTERVAL_TICKS = 600L;
public static final ServerPlayerManager INSTANCE = new ServerPlayerManager();
private volatile Set<Player> players = Collections.emptySet();
public static void init(Plugin plugin) {
if (!Voicechat.SERVER_CONFIG.threadedServerSupport.get().booleanValue()) {
return;
}
Bukkit.getPluginManager().registerEvents((Listener)INSTANCE, plugin);
Voicechat.compatibility.scheduleSyncRepeatingTask(INSTANCE::refresh, 0L, 600L);
}
private ServerPlayerManager() {
}
private synchronized void refresh() {
this.players = new HashSet<Player>(Bukkit.getOnlinePlayers());
}
@EventHandler
public synchronized void onPlayerJoin(PlayerJoinEvent event) {
HashSet<Player> newPlayers = new HashSet<Player>(this.players);
newPlayers.add(event.getPlayer());
this.players = newPlayers;
}
@EventHandler
public synchronized void onPlayerQuit(PlayerQuitEvent event) {
HashSet<Player> newPlayers = new HashSet<Player>(this.players);
newPlayers.remove(event.getPlayer());
this.players = newPlayers;
}
public static Collection<Player> getPlayersInRange(World level, Location pos, double range, @Nullable Predicate<Player> filter) {
return INSTANCE.getPlayersInRangeInternal(level, pos, range, filter);
}
private Collection<Player> getPlayersInRangeInternal(World world, Location pos, double range, @Nullable Predicate<Player> filter) {
if (!Voicechat.SERVER_CONFIG.threadedServerSupport.get().booleanValue()) {
return ServerPlayerManager.getPlayersInRangeDirect(world, pos, range, filter);
}
ArrayList<Player> nearbyPlayers = new ArrayList<Player>();
for (Player player : this.players) {
if (!world.equals((Object)player.getWorld()) || !ServerPlayerManager.isInRange(player.getLocation(), pos, range) || filter != null && !filter.test(player)) continue;
nearbyPlayers.add(player);
}
return nearbyPlayers;
}
private static Collection<Player> getPlayersInRangeDirect(World world, Location pos, double range, @Nullable Predicate<Player> filter) {
ArrayList<Player> nearbyPlayers = new ArrayList<Player>();
List worldPlayers = world.getPlayers();
for (int i = 0; i < worldPlayers.size(); ++i) {
Player player = (Player)worldPlayers.get(i);
if (!ServerPlayerManager.isInRange(player.getLocation(), pos, range) || filter != null && !filter.test(player)) continue;
nearbyPlayers.add(player);
}
return nearbyPlayers;
}
public static boolean isInRange(Location pos1, Location pos2, double range) {
return ServerPlayerManager.square(pos1.getX() - pos2.getX()) + ServerPlayerManager.square(pos1.getY() - pos2.getY()) + ServerPlayerManager.square(pos1.getZ() - pos2.getZ()) <= ServerPlayerManager.square(range);
}
private static double square(double value) {
return value * value;
}
}
@@ -0,0 +1,167 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* org.bukkit.Bukkit
* org.bukkit.entity.Player
* org.bukkit.event.EventHandler
* org.bukkit.event.Listener
* org.bukkit.event.player.PlayerJoinEvent
* org.bukkit.event.player.PlayerQuitEvent
*/
package de.maxhenkel.voicechat.voice.server;
import de.maxhenkel.voicechat.Voicechat;
import de.maxhenkel.voicechat.compatibility.PlayerHideEvent;
import de.maxhenkel.voicechat.compatibility.PlayerShowEvent;
import de.maxhenkel.voicechat.net.NetManager;
import de.maxhenkel.voicechat.net.PacketRateLimiter;
import de.maxhenkel.voicechat.net.RequestSecretPacket;
import de.maxhenkel.voicechat.net.SecretPacket;
import de.maxhenkel.voicechat.plugins.PluginManager;
import de.maxhenkel.voicechat.voice.common.Secret;
import de.maxhenkel.voicechat.voice.server.Server;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class ServerVoiceEvents
implements Listener {
private static final int LEGACY_COMPATIBILITY_VERSION = 18;
private static final int CURRENT_COMPATIBILITY_VERSION = 20;
private final Map<UUID, Integer> clientCompatibilities = new ConcurrentHashMap<UUID, Integer>();
private final PacketRateLimiter rateLimiter;
private Server server;
public ServerVoiceEvents() {
this.rateLimiter = new PacketRateLimiter(Voicechat.SERVER_CONFIG.tcpRateLimit.get());
}
public void init() {
if (this.server != null) {
return;
}
if (!Bukkit.getOnlineMode()) {
Voicechat.LOGGER.warn("Running in offline mode - Voice chat encryption is not secure!", new Object[0]);
}
Voicechat.compatibility.registerPlayerHideEvent(this::onPlayerHide);
Voicechat.compatibility.registerPlayerShowEvent(this::onPlayerShow);
this.server = new Server();
this.server.start();
PluginManager.instance().onServerStarted();
}
public void onRequestSecretPacket(Player player, RequestSecretPacket requestSecretPacket) {
UUID uUID;
int n = requestSecretPacket.getCompatibilityVersion();
Voicechat.LOGGER.info("Received secret request of {} ({})", player.getName(), n);
try {
uUID = player.getUniqueId();
}
catch (UnsupportedOperationException unsupportedOperationException) {
player.kickPlayer("Tried to authenticate voice chat while still connecting");
Voicechat.LOGGER.warn("{} tried to authenticate voice chat while still connecting", player.getName());
return;
}
this.clientCompatibilities.put(uUID, n);
if (!ServerVoiceEvents.isSupportedCompatibilityVersion(n)) {
Voicechat.LOGGER.warn("Connected client {} has incompatible voice chat version (server={}, client={})", player.getName(), Voicechat.COMPATIBILITY_VERSION, n);
ServerVoiceEvents.sendIncompatibleMessage(player, n);
return;
}
this.initializePlayerConnection(player);
}
private static boolean isSupportedCompatibilityVersion(int n) {
return n == 18 || n == 20;
}
public boolean isCompatible(Player player) {
return this.isCompatible(player.getUniqueId());
}
public boolean isCompatible(UUID uUID) {
return ServerVoiceEvents.isSupportedCompatibilityVersion(this.clientCompatibilities.getOrDefault(uUID, -1));
}
public static void sendIncompatibleMessage(Player player, int n) {
if (n <= 6) {
player.sendMessage(String.format(Voicechat.TRANSLATIONS.voicechatNotCompatibleMessage.get(), "2.6.x", "Simple Voice Chat"));
} else {
Voicechat.compatibility.sendIncompatibleMessage(player, "2.6.x", "Simple Voice Chat");
}
}
public void initializePlayerConnection(Player player) {
if (this.server == null) {
return;
}
UUID uUID = player.getUniqueId();
this.server.getPlayerStateManager().onPlayerCompatibilityCheckSucceeded(player);
this.server.getCategoryManager().onPlayerCompatibilityCheckSucceeded(player);
this.server.getGroupManager().onPlayerCompatibilityCheckSucceeded(player);
Secret secret = this.server.generateNewSecret(uUID);
if (secret == null) {
Voicechat.LOGGER.warn("Player already requested secret - ignoring", new Object[0]);
return;
}
secret.setLegacyProtocol(this.clientCompatibilities.getOrDefault(uUID, -1) == 18);
String string = PluginManager.instance().getVoiceHost(player, Voicechat.SERVER_CONFIG.voiceHost.get());
NetManager.sendToClient(player, new SecretPacket(player, secret, this.server.getPort(), Voicechat.SERVER_CONFIG, string));
Voicechat.LOGGER.info("Sent secret to {}", player.getName());
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent playerJoinEvent) {
this.server.getPlayerStateManager().onPlayerJoin(playerJoinEvent);
if (!Voicechat.SERVER_CONFIG.forceVoiceChat.get().booleanValue()) {
return;
}
Player player = playerJoinEvent.getPlayer();
Voicechat.compatibility.runTaskLater(() -> {
if (!player.isOnline()) {
return;
}
if (!this.isCompatible(player)) {
player.kickPlayer(String.format(Voicechat.TRANSLATIONS.forceVoicechatKickMessage.get(), "Simple Voice Chat", Voicechat.INSTANCE.getDescription().getVersion()));
}
}, (long)Voicechat.SERVER_CONFIG.loginTimeout.get().intValue() / 50L);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent playerQuitEvent) {
this.server.getPlayerStateManager().onPlayerQuit(playerQuitEvent);
this.server.getGroupManager().onPlayerQuit(playerQuitEvent);
UUID uUID = playerQuitEvent.getPlayer().getUniqueId();
this.clientCompatibilities.remove(uUID);
this.rateLimiter.onPlayerLoggedOut(playerQuitEvent.getPlayer());
if (this.server == null) {
return;
}
this.server.disconnectClient(uUID);
Voicechat.LOGGER.info("Disconnecting client {}", playerQuitEvent.getPlayer().getName());
}
public void onPlayerHide(PlayerHideEvent playerHideEvent) {
this.server.getPlayerStateManager().onPlayerHide(playerHideEvent);
}
public void onPlayerShow(PlayerShowEvent playerShowEvent) {
this.server.getPlayerStateManager().onPlayerShow(playerShowEvent);
}
public PacketRateLimiter getRateLimiter() {
return this.rateLimiter;
}
public Server getServer() {
return this.server;
}
}