diff --git a/build.gradle b/build.gradle index 8de1a72..cca3e84 100644 --- a/build.gradle +++ b/build.gradle @@ -10,5 +10,5 @@ repositories { } dependencies { - implementation files("libs/ViaProxy-3.0.22-SNAPSHOT+java8.jar") + implementation files("libs/ViaProxy-3.2.0-SNAPSHOT+java8.jar") } \ No newline at end of file diff --git a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglerServerHandler.java b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglerServerHandler.java index 48a020c..e720c2b 100644 --- a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglerServerHandler.java +++ b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglerServerHandler.java @@ -5,6 +5,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.util.ChatColorUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; @@ -24,7 +25,6 @@ import net.raphimc.vialegacy.api.LegacyProtocolVersion; import net.raphimc.vialegacy.protocols.release.protocol1_6_1to1_5_2.ClientboundPackets1_5_2; import net.raphimc.vialegacy.protocols.release.protocol1_6_1to1_5_2.ServerboundPackets1_5_2; import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4; -import net.raphimc.vialoader.util.VersionEnum; import net.raphimc.viaproxy.proxy.session.LegacyProxyConnection; import net.raphimc.viaproxy.proxy.session.ProxyConnection; import net.raphimc.viaproxy.proxy.util.ExceptionUtil; @@ -43,7 +43,7 @@ import java.util.*; import java.util.concurrent.TimeUnit; public class EaglerServerHandler extends MessageToMessageCodec { - private final VersionEnum version; + private final ProtocolVersion version; private final String password; private final NetClient proxyConnection; private final Map uuidStringMap = new HashMap<>(); @@ -53,7 +53,7 @@ public class EaglerServerHandler extends MessageToMessageCodec= 2 && in.getUnsignedByte(0) == 0xFE && in.getUnsignedByte(1) == 0x01) { handshakeState = -1; out.add(new TextWebSocketFrame("Accept: MOTD")); @@ -282,7 +282,7 @@ public class EaglerServerHandler extends MessageToMessageCodec voicePlayers; - private static final ConcurrentHashMap> voiceRequests; - private static final CopyOnWriteArraySet voicePairs; - private final String user; - private static final Collection iceServers; - private static final AttributeKey VOICE_ENABLED; - private static void sendData(final ChannelHandlerContext ctx, final byte[] data) throws IOException { - final ByteBuf bb = ctx.alloc().buffer(); - bb.writeByte(250); - try { - Types1_6_4.STRING.write(bb, "EAG|Voice"); - } catch (Exception e) { - throw new IOException(e); - } - bb.writeShort(data.length); - bb.writeBytes(data); - ctx.writeAndFlush(new BinaryWebSocketFrame(bb)); - } + public static final VoiceServerImpl voiceService = new VoiceServerImpl(); + + public String user; + public UUID uuid; + public final boolean old; + private int pluginMessageId = -1; public EaglerVoiceHandler(final String username) { this.user = username; + if (this.user == null) { + this.uuid = null; + old = false; + } else { + old = true; + this.uuid = nameToUUID(this.user); + } } @Override @@ -43,187 +37,107 @@ public class EaglerVoiceHandler extends ChannelInboundHandlerAdapter { ExceptionUtil.handleNettyException(ctx, cause, null); } + private static UUID nameToUUID(String name) { + return UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8)); + } + + private boolean sent = false; + @Override public void channelRead(final ChannelHandlerContext ctx, final Object obj) throws Exception { - if (((EaglercraftHandler) ctx.pipeline().get("eaglercraft-handler")).state == EaglercraftHandler.State.LOGIN_COMPLETE && !ctx.channel().hasAttr(EaglerVoiceHandler.VOICE_ENABLED)) { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final DataOutputStream dos = new DataOutputStream(baos); - dos.write(0); - dos.writeBoolean(true); - dos.write(EaglerVoiceHandler.iceServers.size()); - for (final String str : EaglerVoiceHandler.iceServers) { - dos.writeUTF(str); + if (!FunnyConfig.eaglerVoice) { + super.channelRead(ctx, obj); + return; + } + if (((EaglercraftHandler) ctx.pipeline().get("eaglercraft-handler")).state == EaglercraftHandler.State.LOGIN_COMPLETE && !sent) { + if (this.user == null) { + this.user = ((EaglercraftHandler) ctx.pipeline().get("eaglercraft-handler")).username; + this.uuid = nameToUUID(this.user); } - sendData(ctx, baos.toByteArray()); - this.sendVoicePlayers(this.user); - ctx.channel().attr(EaglerVoiceHandler.VOICE_ENABLED).set(true); + sent = true; + voiceService.handlePlayerLoggedIn(ctx); } if (obj instanceof BinaryWebSocketFrame) { final ByteBuf bb = ((BinaryWebSocketFrame) obj).content(); - if (bb.readableBytes() >= 3 && bb.readByte() == -6) { - String tag; - byte[] msg; - try { - tag = Types1_6_4.STRING.read(bb); - msg = new byte[bb.readShort()]; - bb.readBytes(msg); - } catch (Exception e) { - bb.resetReaderIndex(); - super.channelRead(ctx, obj); - return; - } - try { - if (!tag.equals("EAG|Voice")) { + if (old) { + if (bb.readableBytes() >= 3 && bb.readByte() == -6) { + String tag; + byte[] msg; + try { + tag = Types1_6_4.STRING.read(bb); + msg = new byte[bb.readShort()]; + bb.readBytes(msg); + } catch (Exception e) { bb.resetReaderIndex(); super.channelRead(ctx, obj); return; } - if (!FunnyConfig.eaglerVoice) { + try { + if (!tag.equals("EAG|Voice")) { + bb.resetReaderIndex(); + super.channelRead(ctx, obj); + return; + } + final DataInputStream streamIn = new DataInputStream(new ByteArrayInputStream(msg)); + final int sig = streamIn.read(); + switch (sig) { + case 0: { + voiceService.handleVoiceSignalPacketTypeRequest(nameToUUID(streamIn.readUTF()), ctx); + bb.release(); + return; + } + case 1: { + voiceService.handleVoiceSignalPacketTypeConnect(ctx); + bb.release(); + return; + } + case 2: { + try { + voiceService.handleVoiceSignalPacketTypeDisconnect(nameToUUID(streamIn.readUTF()), ctx); + } catch (EOFException e) { + voiceService.handleVoiceSignalPacketTypeDisconnect(null, ctx); + } + bb.release(); + return; + } + case 3: { + voiceService.handleVoiceSignalPacketTypeICE(nameToUUID(streamIn.readUTF()), streamIn.readUTF(), ctx); + bb.release(); + return; + } + case 4: { + voiceService.handleVoiceSignalPacketTypeDesc(nameToUUID(streamIn.readUTF()), streamIn.readUTF(), ctx); + bb.release(); + return; + } + default: { + bb.release(); + return; + } + } + } catch (Throwable var8) { + voiceService.handlePlayerLoggedOut(uuid); bb.release(); return; } - final DataInputStream streamIn = new DataInputStream(new ByteArrayInputStream(msg)); - final int sig = streamIn.read(); - switch (sig) { - case 0: { - if (!EaglerVoiceHandler.voicePlayers.containsKey(this.user)) { - bb.release(); - return; - } - final String targetUser = streamIn.readUTF(); - if (this.user.equals(targetUser)) { - bb.release(); - return; - } - if (this.checkVoicePair(this.user, targetUser)) { - bb.release(); - return; - } - if (!EaglerVoiceHandler.voicePlayers.containsKey(targetUser)) { - bb.release(); - return; - } - if (!EaglerVoiceHandler.voiceRequests.containsKey(this.user)) { - EaglerVoiceHandler.voiceRequests.put(this.user, new ExpiringSet<>(2000L)); - } - if (!EaglerVoiceHandler.voiceRequests.get(this.user).contains(targetUser)) { - EaglerVoiceHandler.voiceRequests.get(this.user).add(targetUser); - if (EaglerVoiceHandler.voiceRequests.containsKey(targetUser) && EaglerVoiceHandler.voiceRequests.get(targetUser).contains(this.user)) { - if (EaglerVoiceHandler.voiceRequests.containsKey(targetUser)) { - EaglerVoiceHandler.voiceRequests.get(targetUser).remove(this.user); - if (EaglerVoiceHandler.voiceRequests.get(targetUser).isEmpty()) { - EaglerVoiceHandler.voiceRequests.remove(targetUser); - } - } - if (EaglerVoiceHandler.voiceRequests.containsKey(this.user)) { - EaglerVoiceHandler.voiceRequests.get(this.user).remove(targetUser); - if (EaglerVoiceHandler.voiceRequests.get(this.user).isEmpty()) { - EaglerVoiceHandler.voiceRequests.remove(this.user); - } - } - EaglerVoiceHandler.voicePairs.add(new String[]{this.user, targetUser}); - ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); - DataOutputStream dos2 = new DataOutputStream(baos2); - dos2.write(1); - dos2.writeUTF(this.user); - dos2.writeBoolean(false); - sendData(EaglerVoiceHandler.voicePlayers.get(targetUser), baos2.toByteArray()); - baos2 = new ByteArrayOutputStream(); - dos2 = new DataOutputStream(baos2); - dos2.write(1); - dos2.writeUTF(targetUser); - dos2.writeBoolean(true); - sendData(ctx, baos2.toByteArray()); - } - bb.release(); - return; - } - bb.release(); - return; - } - case 1: { - if (!EaglerVoiceHandler.voicePlayers.containsKey(this.user)) { - final ByteArrayOutputStream baos3 = new ByteArrayOutputStream(); - final DataOutputStream dos3 = new DataOutputStream(baos3); - dos3.write(1); - dos3.writeUTF(this.user); - final byte[] out = baos3.toByteArray(); - for (final ChannelHandlerContext conn : EaglerVoiceHandler.voicePlayers.values()) { - sendData(conn, out); - } - EaglerVoiceHandler.voicePlayers.put(this.user, ctx); - for (final String username : EaglerVoiceHandler.voicePlayers.keySet()) { - this.sendVoicePlayers(username); - } - bb.release(); - return; - } - bb.release(); - return; - } - case 2: { - if (!EaglerVoiceHandler.voicePlayers.containsKey(this.user)) { - bb.release(); - return; - } - try { - final String targetUser = streamIn.readUTF(); - if (!EaglerVoiceHandler.voicePlayers.containsKey(targetUser)) { - bb.release(); - return; - } - if (EaglerVoiceHandler.voicePairs.removeIf(pair -> (pair[0].equals(this.user) && pair[1].equals(targetUser)) || (pair[0].equals(targetUser) && pair[1].equals(this.user)))) { - ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); - DataOutputStream dos2 = new DataOutputStream(baos2); - dos2.write(2); - dos2.writeUTF(this.user); - sendData(EaglerVoiceHandler.voicePlayers.get(targetUser), baos2.toByteArray()); - baos2 = new ByteArrayOutputStream(); - dos2 = new DataOutputStream(baos2); - dos2.write(2); - dos2.writeUTF(targetUser); - sendData(ctx, baos2.toByteArray()); - break; - } - } catch (EOFException var7) { - this.removeUser(this.user); - } - bb.release(); - return; - } - case 3: - case 4: { - if (EaglerVoiceHandler.voicePlayers.containsKey(this.user)) { - final String username = streamIn.readUTF(); - if (this.checkVoicePair(this.user, username)) { - final String data = streamIn.readUTF(); - final ByteArrayOutputStream baos4 = new ByteArrayOutputStream(); - final DataOutputStream dos4 = new DataOutputStream(baos4); - dos4.write(sig); - dos4.writeUTF(this.user); - dos4.writeUTF(data); - sendData(EaglerVoiceHandler.voicePlayers.get(username), baos4.toByteArray()); - } - bb.release(); - return; - } - bb.release(); - return; - } - default: { - bb.release(); - return; - } - } - } catch (Throwable var8) { - this.removeUser(this.user); - bb.release(); - return; } - bb.release(); - return; + bb.resetReaderIndex(); + } else { + if (this.pluginMessageId <= 0) { + this.pluginMessageId = ((EaglercraftHandler) ctx.pipeline().get("eaglercraft-handler")).pluginMessageId; + } + try { + if (PacketTypes.readVarInt(bb) == this.pluginMessageId && PacketTypes.readString(bb, 32767).equals("EAG|Voice-1.8")) { + final byte[] data = new byte[bb.readableBytes()]; + bb.readBytes(data); + VoiceSignalPackets.processPacket(data, ctx); + bb.release(); + return; + } + } catch (Exception ignored) { + } + bb.resetReaderIndex(); } - bb.resetReaderIndex(); } super.channelRead(ctx, obj); } @@ -231,95 +145,6 @@ public class EaglerVoiceHandler extends ChannelInboundHandlerAdapter { @Override public void channelInactive(final ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); - this.removeUser(this.user); - } - - public void sendVoicePlayers(final String name) { - if (EaglerVoiceHandler.voicePlayers.containsKey(name)) { - try { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final DataOutputStream dos = new DataOutputStream(baos); - dos.write(5); - final Set mostlyGlobalPlayers = ConcurrentHashMap.newKeySet(); - for (final String username : EaglerVoiceHandler.voicePlayers.keySet()) { - if (!username.equals(name) && EaglerVoiceHandler.voicePairs.stream().noneMatch(pair -> (pair[0].equals(name) && pair[1].equals(username)) || (pair[0].equals(username) && pair[1].equals(name)))) { - mostlyGlobalPlayers.add(username); - } - } - if (!mostlyGlobalPlayers.isEmpty()) { - dos.writeInt(mostlyGlobalPlayers.size()); - for (final String username : mostlyGlobalPlayers) { - dos.writeUTF(username); - } - sendData(EaglerVoiceHandler.voicePlayers.get(name), baos.toByteArray()); - } - } catch (IOException ignored) { - } - } - } - - public void removeUser(final String name) { - EaglerVoiceHandler.voicePlayers.remove(name); - for (final String username : EaglerVoiceHandler.voicePlayers.keySet()) { - if (!name.equals(username)) { - this.sendVoicePlayers(username); - } - } - for (final String[] voicePair : EaglerVoiceHandler.voicePairs) { - String target = null; - if (voicePair[0].equals(name)) { - target = voicePair[1]; - } else if (voicePair[1].equals(name)) { - target = voicePair[0]; - } - if (target != null && EaglerVoiceHandler.voicePlayers.containsKey(target)) { - try { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final DataOutputStream dos = new DataOutputStream(baos); - dos.write(2); - dos.writeUTF(name); - sendData(EaglerVoiceHandler.voicePlayers.get(target), baos.toByteArray()); - } catch (IOException ignored) { - } - } - } - EaglerVoiceHandler.voicePairs.removeIf(pair -> pair[0].equals(name) || pair[1].equals(name)); - } - - private boolean checkVoicePair(final String user1, final String user2) { - return EaglerVoiceHandler.voicePairs.stream().anyMatch(pair -> (pair[0].equals(user1) && pair[1].equals(user2)) || (pair[0].equals(user2) && pair[1].equals(user1))); - } - - static { - voicePlayers = new ConcurrentHashMap<>(); - voiceRequests = new ConcurrentHashMap<>(); - voicePairs = new CopyOnWriteArraySet<>(); - (iceServers = new ArrayList<>()).add("stun:stun.l.google.com:19302"); - EaglerVoiceHandler.iceServers.add("stun:stun1.l.google.com:19302"); - EaglerVoiceHandler.iceServers.add("stun:stun2.l.google.com:19302"); - EaglerVoiceHandler.iceServers.add("stun:stun3.l.google.com:19302"); - EaglerVoiceHandler.iceServers.add("stun:stun4.l.google.com:19302"); - EaglerVoiceHandler.iceServers.add("stun:openrelay.metered.ca:80"); - final Map> turnServerList = new HashMap<>(); - HashMap n = new HashMap<>(); - n.put("url", "turn:openrelay.metered.ca:80"); - n.put("username", "openrelayproject"); - n.put("password", "openrelayproject"); - turnServerList.put("openrelay1", n); - n = new HashMap<>(); - n.put("url", "turn:openrelay.metered.ca:443"); - n.put("username", "openrelayproject"); - n.put("password", "openrelayproject"); - turnServerList.put("openrelay2", n); - n = new HashMap<>(); - n.put("url", "turn:openrelay.metered.ca:443?transport=tcp"); - n.put("username", "openrelayproject"); - n.put("password", "openrelayproject"); - turnServerList.put("openrelay3", n); - for (final Map.Entry> trn : turnServerList.entrySet()) { - final Map o = trn.getValue(); - EaglerVoiceHandler.iceServers.add(o.get("url") + ";" + o.get("username") + ";" + o.get("password")); - } - VOICE_ENABLED = AttributeKey.valueOf("ayun-voice-enabled"); + voiceService.handlePlayerLoggedOut(uuid); } } diff --git a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftHandler.java b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftHandler.java index df99607..9645a46 100644 --- a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftHandler.java +++ b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftHandler.java @@ -1,6 +1,7 @@ package me.ayunami2000.ayunViaProxyEagUtils; import com.google.common.net.HostAndPort; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.libs.gson.JsonArray; import com.viaversion.viaversion.libs.gson.JsonObject; import com.viaversion.viaversion.libs.gson.JsonParser; @@ -22,9 +23,9 @@ import net.raphimc.netminecraft.constants.ConnectionState; import net.raphimc.netminecraft.constants.MCPackets; import net.raphimc.netminecraft.constants.MCPipeline; import net.raphimc.netminecraft.packet.PacketTypes; +import net.raphimc.vialegacy.api.LegacyProtocolVersion; import net.raphimc.vialegacy.protocols.release.protocol1_6_1to1_5_2.ServerboundPackets1_5_2; import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4; -import net.raphimc.vialoader.util.VersionEnum; import net.raphimc.viaproxy.ViaProxy; import net.raphimc.viaproxy.proxy.client2proxy.Client2ProxyChannelInitializer; import net.raphimc.viaproxy.proxy.util.ExceptionUtil; @@ -53,7 +54,7 @@ public class EaglercraftHandler extends MessageToMessageCodec httpHeadersKey = AttributeKey.newInstance("eag-http-headers"); private HostAndPort host; public State state; - public VersionEnum version; + public ProtocolVersion version; public int pluginMessageId; public String username; @@ -143,7 +144,7 @@ public class EaglercraftHandler extends MessageToMessageCodec= 2 && data.getByte(0) == 2 && data.getByte(1) == 69) { data.setByte(1, 61); this.state = State.LOGIN_COMPLETE; - this.version = VersionEnum.r1_5_2; + this.version = LegacyProtocolVersion.r1_5_2; out.add(data.retain()); break; } @@ -197,8 +198,8 @@ public class EaglercraftHandler extends MessageToMessageCodec= 3 || in.getByte(0) != 71) { - if ((in.readableBytes() >= 3 && in.getCharSequence(0, 3, StandardCharsets.UTF_8).equals("GET")) || (in.readableBytes() >= 4 && in.getCharSequence(0, 4, StandardCharsets.UTF_8).equals("POST"))) { - if (EaglercraftInitialHandler.sslContext != null) { + boolean ssl = EaglercraftInitialHandler.sslContext != null && in.readableBytes() >= 3 && in.getByte(0) == 22; + if (ssl || (in.readableBytes() >= 3 && in.getCharSequence(0, 3, StandardCharsets.UTF_8).equals("GET")) || (in.readableBytes() >= 4 && in.getCharSequence(0, 4, StandardCharsets.UTF_8).equals("POST"))) { + if (ssl) { ctx.pipeline().addBefore("eaglercraft-initial-handler", "ws-ssl-handler", EaglercraftInitialHandler.sslContext.newHandler(ctx.alloc())); } ctx.pipeline().addBefore("eaglercraft-initial-handler", "ws-http-codec", new HttpServerCodec()); @@ -69,7 +70,7 @@ public class EaglercraftInitialHandler extends ByteToMessageDecoder { } } - Supplier handlerSupplier = () -> PluginManager.EVENT_MANAGER.call(new Client2ProxyHandlerCreationEvent(new PassthroughClient2ProxyHandler(), true)).getHandler(); + Supplier handlerSupplier = () -> ViaProxy.EVENT_MANAGER.call(new Client2ProxyHandlerCreationEvent(new PassthroughClient2ProxyHandler(), true)).getHandler(); PassthroughClient2ProxyChannelInitializer channelInitializer = new PassthroughClient2ProxyChannelInitializer(handlerSupplier); try { initChannelMethod.invoke(channelInitializer, ctx.channel()); diff --git a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/Main.java b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/Main.java index 129e3c2..e1a598c 100644 --- a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/Main.java +++ b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/Main.java @@ -1,23 +1,25 @@ package me.ayunami2000.ayunViaProxyEagUtils; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import io.netty.buffer.ByteBuf; import io.netty.channel.*; +import io.netty.handler.codec.compression.ZlibCodecFactory; import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler; import io.netty.handler.codec.http.websocketx.WebSocketVersion; -import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler; +import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; +import io.netty.handler.codec.http.websocketx.extensions.compression.DeflateFrameClientExtensionHandshaker; +import io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateClientExtensionHandshaker; import io.netty.handler.ssl.SslHandler; import io.netty.util.AsciiString; import io.netty.util.AttributeKey; import net.lenni0451.lambdaevents.EventHandler; import net.raphimc.netminecraft.constants.MCPipeline; import net.raphimc.netminecraft.netty.connection.NetClient; -import net.raphimc.netminecraft.util.ServerAddress; import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4; -import net.raphimc.vialoader.util.VersionEnum; -import net.raphimc.viaproxy.plugins.PluginManager; +import net.raphimc.viaproxy.ViaProxy; import net.raphimc.viaproxy.plugins.ViaProxyPlugin; import net.raphimc.viaproxy.plugins.events.Client2ProxyChannelInitializeEvent; import net.raphimc.viaproxy.plugins.events.Proxy2ServerChannelInitializeEvent; @@ -31,6 +33,8 @@ import javax.net.ssl.*; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.security.KeyManagementException; @@ -70,8 +74,8 @@ public class Main extends ViaProxyPlugin { } public void onEnable() { - PluginManager.EVENT_MANAGER.register(this); - (new FunnyConfig(new File("ViaLoader", "vpeagutils.yml"))).reloadConfig(); + ViaProxy.EVENT_MANAGER.register(this); + (new FunnyConfig(new File("ViaLoader", "vpeagutils.yml"))).reload(); EaglerXSkinHandler.skinService = new SkinService(); } @@ -82,7 +86,7 @@ public class Main extends ViaProxyPlugin { NetClient proxyConnection; Channel c2p; - ServerAddress addr; + SocketAddress addr; if (event.isLegacyPassthrough()) { proxyConnection = LegacyProxyConnection.fromChannel(ch); c2p = ((LegacyProxyConnection) proxyConnection).getC2P(); @@ -99,8 +103,8 @@ public class Main extends ViaProxyPlugin { c2p.attr(secureWs).set(true); } - if (c2p.hasAttr(secureWs) && c2p.attr(secureWs).get() != null) { - doWsServerStuff(ch, proxyConnection, c2p, addr); + if (c2p.hasAttr(secureWs) && c2p.attr(secureWs).get() != null && addr instanceof InetSocketAddress) { + doWsServerStuff(ch, proxyConnection, c2p, (InetSocketAddress) addr); if (!event.isLegacyPassthrough()) { ch.pipeline().addFirst("handshake-waiter", new ChannelOutboundHandlerAdapter() { private boolean hasHandshake = false; @@ -142,9 +146,9 @@ public class Main extends ViaProxyPlugin { } } - private static void doWsServerStuff(Channel ch, NetClient proxyConnection, Channel c2p, ServerAddress addr) throws URISyntaxException { + private static void doWsServerStuff(Channel ch, NetClient proxyConnection, Channel c2p, InetSocketAddress addr) throws URISyntaxException { ch.attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(-2); - if (proxyConnection instanceof ProxyConnection && ((ProxyConnection) proxyConnection).getServerVersion().isNewerThan(VersionEnum.r1_6_4)) { + if (proxyConnection instanceof ProxyConnection && ((ProxyConnection) proxyConnection).getServerVersion().newerThanOrEqualTo(ProtocolVersion.v1_7_2)) { ch.pipeline().remove(MCPipeline.SIZER_HANDLER_NAME); } else if (ch.pipeline().get(MCPipeline.ENCRYPTION_HANDLER_NAME) != null) { ch.pipeline().remove(MCPipeline.ENCRYPTION_HANDLER_NAME); @@ -164,7 +168,7 @@ public class Main extends ViaProxyPlugin { StringBuilder url = new StringBuilder("ws"); boolean secure = c2p.attr(secureWs).get(); if (secure) { - final SSLEngine sslEngine = sc.createSSLEngine(addr.getAddress(), addr.getPort()); + final SSLEngine sslEngine = sc.createSSLEngine(addr.getHostString(), addr.getPort()); sslEngine.setUseClientMode(true); sslEngine.setNeedClientAuth(false); ch.pipeline().addFirst("eag-server-ssl", new SslHandler(sslEngine) { @@ -181,7 +185,7 @@ public class Main extends ViaProxyPlugin { } else { ch.pipeline().addFirst("eag-server-http-codec", new HttpClientCodec()); } - url.append("://").append(addr.getAddress()); + url.append("://").append(addr.getHostString()); if ((secure && addr.getPort() != 443) || (!secure && addr.getPort() != 80)) { url.append(":").append(addr.getPort()); } @@ -193,7 +197,7 @@ public class Main extends ViaProxyPlugin { URI uri = new URI(url.toString()); HttpHeaders headers = c2p.hasAttr(EaglercraftHandler.httpHeadersKey) ? c2p.attr(EaglercraftHandler.httpHeadersKey).get() : new DefaultHttpHeaders().clear().set(HttpHeaderNames.ORIGIN, "https://via.shhnowisnottheti.me"); ch.pipeline().addAfter("eag-server-http-codec", "eag-server-http-aggregator", new HttpObjectAggregator(2097152, true)); - ch.pipeline().addAfter("eag-server-http-aggregator", "eag-server-ws-compression", WebSocketClientCompressionHandler.INSTANCE); + ch.pipeline().addAfter("eag-server-http-aggregator", "eag-server-ws-compression", new WebSocketClientExtensionHandler(new PerMessageDeflateClientExtensionHandshaker(1, ZlibCodecFactory.isSupportingWindowSizeAndMemLevel(), 15, false, false), new DeflateFrameClientExtensionHandshaker(1, false), new DeflateFrameClientExtensionHandshaker(1, true))); ch.pipeline().addAfter("eag-server-ws-compression", "eag-server-ws-handshaker", new WebSocketClientProtocolHandler(WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, true, headers, 2097152))); ch.pipeline().addAfter("eag-server-ws-handshaker", "eag-server-ws-ready", new WebSocketConnectedNotifier()); ch.pipeline().addAfter("eag-server-ws-ready", "eag-server-handler", new EaglerServerHandler(proxyConnection, c2p.attr(eagxPass).get())); @@ -239,6 +243,7 @@ public class Main extends ViaProxyPlugin { } else { ctx.pipeline().addBefore("eaglercraft-handler", "ayun-eag-x-login", new EaglerXLoginHandler()); ctx.pipeline().addBefore("eaglercraft-handler", "ayun-eag-skin-x", new EaglerXSkinHandler()); + ctx.pipeline().addBefore("eaglercraft-handler", "ayun-eag-voice", new EaglerVoiceHandler(null)); } } catch (Exception ignored) { } diff --git a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/VoiceServerImpl.java b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/VoiceServerImpl.java new file mode 100644 index 0000000..51ef001 --- /dev/null +++ b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/VoiceServerImpl.java @@ -0,0 +1,400 @@ +package me.ayunami2000.ayunViaProxyEagUtils; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import net.raphimc.netminecraft.constants.MCPackets; +import net.raphimc.netminecraft.packet.PacketTypes; +import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class VoiceServerImpl { + + public static final Map uuidToUsernameMap = new HashMap<>(); + + private static void fuckFuckFuck(ByteBuf out, String str) { + int strlen = str.length(); + int utflen = 0; + int c, count = 0; + + /* use charAt instead of copying String to char array */ + for (int i = 0; i < strlen; i++) { + c = str.charAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) { + utflen++; + } else if (c > 0x07FF) { + utflen += 3; + } else { + utflen += 2; + } + } + + if (utflen > 65535) + throw new RuntimeException( + "encoded string too long: " + utflen + " bytes"); + + byte[] bytearr = new byte[utflen+2]; + + bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF); + bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF); + + int i=0; + for (i=0; i= 0x0001) && (c <= 0x007F))) break; + bytearr[count++] = (byte) c; + } + + for (;i < strlen; i++){ + c = str.charAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) { + bytearr[count++] = (byte) c; + + } else if (c > 0x07FF) { + bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F)); + bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F)); + bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); + } else { + bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F)); + bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); + } + } + out.writeBytes(bytearr, 0, utflen+2); + } + + private int pluginMessageId = -1; + private void sendData(final ChannelHandlerContext ctx, final byte[] data) { + final ByteBuf bb = ctx.alloc().buffer(); + if (((EaglerVoiceHandler) ctx.pipeline().get("ayun-eag-voice")).old) { + bb.writeByte(250); + try { + Types1_6_4.STRING.write(bb, "EAG|Voice"); + } catch (Exception e) { + e.printStackTrace(); + return; + } + bb.writeShort(data.length); + bb.writeBytes(data); + bb.readerIndex(23); + switch (bb.readByte()) { + case 0: + if (bb.readableBytes() == 1) break; + bb.skipBytes(1); + int vi = PacketTypes.readVarInt(bb); + String[] fard = new String[vi]; + for (int i = 0; i < vi; i++) { + int xd = PacketTypes.readVarInt(bb); + byte[] arr = new byte[xd]; + bb.readBytes(arr); + fard[i] = new String(arr, StandardCharsets.UTF_8); + } + bb.readerIndex(0); + bb.writerIndex(25); + bb.writeByte(vi); + for (String s : fard) { + fuckFuckFuck(bb, s); + } + break; + case 1: + UUID uuid = PacketTypes.readUuid(bb); + boolean o = bb.readBoolean(); + bb.readerIndex(0); + bb.writerIndex(24); + fuckFuckFuck(bb, uuidToUsernameMap.get(uuid)); + bb.writeBoolean(o); + break; + case 2: + if (bb.readableBytes() == 0) break; + uuid = PacketTypes.readUuid(bb); + bb.readerIndex(0); + bb.writerIndex(24); + fuckFuckFuck(bb, uuidToUsernameMap.get(uuid)); + break; + case 3: + case 4: + uuid = PacketTypes.readUuid(bb); + vi = PacketTypes.readVarInt(bb); + byte[] arr = new byte[vi]; + bb.readBytes(arr); + bb.readerIndex(0); + bb.writerIndex(24); + fuckFuckFuck(bb, uuidToUsernameMap.get(uuid)); + fuckFuckFuck(bb, new String(arr, StandardCharsets.UTF_8)); + break; + case 5: + vi = PacketTypes.readVarInt(bb); + bb.skipBytes(vi * 16); + fard = new String[vi]; + for (int i = 0; i < vi; i++) { + int xd = PacketTypes.readVarInt(bb); + arr = new byte[xd]; + bb.readBytes(arr); + fard[i] = new String(arr, StandardCharsets.UTF_8); + } + bb.readerIndex(0); + bb.writerIndex(24); + bb.writeInt(vi); + for (String s : fard) { + fuckFuckFuck(bb, s); + } + break; + } + bb.readerIndex(0); + bb.setShort(21, bb.writerIndex() - 23); + } else { + if (pluginMessageId <= 0) { + pluginMessageId = MCPackets.S2C_PLUGIN_MESSAGE.getId((((EaglercraftHandler) ctx.pipeline().get("eaglercraft-handler")).version).getVersion()); + } + PacketTypes.writeVarInt(bb, pluginMessageId); + PacketTypes.writeString(bb, "EAG|Voice-1.8"); + bb.writeBytes(data); + } + ctx.writeAndFlush(new BinaryWebSocketFrame(bb)); + } + + private final byte[] iceServersPacket; + + private final Map voicePlayers = new HashMap<>(); + private final Map> voiceRequests = new HashMap<>(); + private final Set voicePairs = new HashSet<>(); + + private static class VoicePair { + + private final UUID uuid1; + private final UUID uuid2; + + @Override + public int hashCode() { + return uuid1.hashCode() ^ uuid2.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + VoicePair other = (VoicePair) obj; + return (uuid1.equals(other.uuid1) && uuid2.equals(other.uuid2)) + || (uuid1.equals(other.uuid2) && uuid2.equals(other.uuid1)); + } + + private VoicePair(UUID uuid1, UUID uuid2) { + this.uuid1 = uuid1; + this.uuid2 = uuid2; + } + + private boolean anyEquals(UUID uuid) { + return uuid1.equals(uuid) || uuid2.equals(uuid); + } + } + + VoiceServerImpl() { + if (FunnyConfig.eaglerVoice) { + Set list = new HashSet<>(); + list.add("stun:stun.l.google.com:19302"); + list.add("stun:stun1.l.google.com:19302"); + list.add("stun:stun2.l.google.com:19302"); + list.add("stun:stun3.l.google.com:19302"); + list.add("stun:stun4.l.google.com:19302"); + list.add("turn:relay1.expressturn.com:3478;ef2KWF55ZPQWCLBMIG;MGn8T66L0GhgDodf"); + this.iceServersPacket = VoiceSignalPackets.makeVoiceSignalPacketAllowed(true, list.toArray(new String[0])); + } else { + this.iceServersPacket = VoiceSignalPackets.makeVoiceSignalPacketAllowed(false, null); + } + } + + public void handlePlayerLoggedIn(ChannelHandlerContext player) { + EaglerVoiceHandler evh = (EaglerVoiceHandler) player.pipeline().get("ayun-eag-voice"); + uuidToUsernameMap.put(evh.uuid, evh.user); + sendData(player, iceServersPacket); + } + + public void handlePlayerLoggedOut(UUID playerUUID) { + removeUser(playerUUID); + uuidToUsernameMap.remove(playerUUID); + } + + void handleVoiceSignalPacketTypeRequest(UUID player, ChannelHandlerContext sender) { + UUID senderUUID = ((EaglerVoiceHandler) sender.pipeline().get("ayun-eag-voice")).uuid; + synchronized (voicePlayers) { + if (senderUUID.equals(player)) + return; // prevent duplicates + if (!voicePlayers.containsKey(senderUUID)) + return; + ChannelHandlerContext targetPlayerCon = voicePlayers.get(player); + if (targetPlayerCon == null) + return; + VoicePair newPair = new VoicePair(player, senderUUID); + if (voicePairs.contains(newPair)) + return; // already paired + ExpiringSet senderRequestSet = voiceRequests.get(senderUUID); + if (senderRequestSet == null) { + voiceRequests.put(senderUUID, senderRequestSet = new ExpiringSet<>(2000)); + } + if (!senderRequestSet.add(player)) { + return; + } + + // check if other has requested earlier + ExpiringSet theSet; + if ((theSet = voiceRequests.get(player)) != null && theSet.contains(senderUUID)) { + theSet.remove(senderUUID); + if (theSet.isEmpty()) + voiceRequests.remove(player); + senderRequestSet.remove(player); + if (senderRequestSet.isEmpty()) + voiceRequests.remove(senderUUID); + // send each other add data + voicePairs.add(newPair); + sendData(targetPlayerCon, + VoiceSignalPackets.makeVoiceSignalPacketConnect(senderUUID, false)); + sendData(sender, VoiceSignalPackets.makeVoiceSignalPacketConnect(player, true)); + } + } + } + + void handleVoiceSignalPacketTypeConnect(ChannelHandlerContext sender) { + UUID senderUUID = ((EaglerVoiceHandler) sender.pipeline().get("ayun-eag-voice")).uuid; + synchronized (voicePlayers) { + if (voicePlayers.containsKey(senderUUID)) { + return; + } + boolean hasNoOtherPlayers = voicePlayers.isEmpty(); + voicePlayers.put(senderUUID, sender); + if (hasNoOtherPlayers) { + return; + } + byte[] packetToBroadcast = VoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values()); + for (ChannelHandlerContext userCon : voicePlayers.values()) { + sendData(userCon, packetToBroadcast); + } + } + } + + void handleVoiceSignalPacketTypeICE(UUID player, String str, ChannelHandlerContext sender) { + UUID senderUUID = ((EaglerVoiceHandler) sender.pipeline().get("ayun-eag-voice")).uuid; + ChannelHandlerContext pass; + VoicePair pair = new VoicePair(player, senderUUID); + synchronized (voicePlayers) { + pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null; + } + if (pass != null) { + sendData(pass, VoiceSignalPackets.makeVoiceSignalPacketICE(senderUUID, str)); + } + } + + void handleVoiceSignalPacketTypeDesc(UUID player, String str, ChannelHandlerContext sender) { + UUID senderUUID = ((EaglerVoiceHandler) sender.pipeline().get("ayun-eag-voice")).uuid; + ChannelHandlerContext pass; + VoicePair pair = new VoicePair(player, senderUUID); + synchronized (voicePlayers) { + pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null; + } + if (pass != null) { + sendData(pass, + VoiceSignalPackets.makeVoiceSignalPacketDesc(senderUUID, str)); + } + } + + void handleVoiceSignalPacketTypeDisconnect(UUID player, ChannelHandlerContext sender) { + UUID senderUUID = ((EaglerVoiceHandler) sender.pipeline().get("ayun-eag-voice")).uuid; + if (player != null) { + synchronized (voicePlayers) { + if (!voicePlayers.containsKey(player)) { + return; + } + byte[] userDisconnectPacket = null; + Iterator pairsItr = voicePairs.iterator(); + while (pairsItr.hasNext()) { + VoicePair voicePair = pairsItr.next(); + UUID target = null; + if (voicePair.uuid1.equals(player)) { + target = voicePair.uuid2; + } else if (voicePair.uuid2.equals(player)) { + target = voicePair.uuid1; + } + if (target != null) { + pairsItr.remove(); + ChannelHandlerContext conn = voicePlayers.get(target); + if (conn != null) { + if (userDisconnectPacket == null) { + userDisconnectPacket = VoiceSignalPackets.makeVoiceSignalPacketDisconnect(player); + } + sendData(conn, userDisconnectPacket); + } + sendData(sender, + VoiceSignalPackets.makeVoiceSignalPacketDisconnect(target)); + } + } + } + } else { + removeUser(senderUUID); + } + } + + public void removeUser(UUID user) { + synchronized (voicePlayers) { + if (voicePlayers.remove(user) == null) { + return; + } + voiceRequests.remove(user); + if (voicePlayers.size() > 0) { + byte[] voicePlayersPkt = VoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values()); + for (Map.Entry userCon : voicePlayers.entrySet()) { + if (!user.equals(userCon.getKey())) { + sendData(userCon.getValue(), voicePlayersPkt); + } + } + } + byte[] userDisconnectPacket = null; + Iterator pairsItr = voicePairs.iterator(); + while (pairsItr.hasNext()) { + VoicePair voicePair = pairsItr.next(); + UUID target = null; + if (voicePair.uuid1.equals(user)) { + target = voicePair.uuid2; + } else if (voicePair.uuid2.equals(user)) { + target = voicePair.uuid1; + } + if (target != null) { + pairsItr.remove(); + if (voicePlayers.size() > 0) { + ChannelHandlerContext conn = voicePlayers.get(target); + if (conn != null) { + if (userDisconnectPacket == null) { + userDisconnectPacket = VoiceSignalPackets.makeVoiceSignalPacketDisconnect(user); + } + sendData(conn, userDisconnectPacket); + } + } + } + } + } + } + +} diff --git a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/VoiceSignalPackets.java b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/VoiceSignalPackets.java new file mode 100644 index 0000000..82b7c85 --- /dev/null +++ b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/VoiceSignalPackets.java @@ -0,0 +1,186 @@ +package me.ayunami2000.ayunViaProxyEagUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.UUID; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import net.raphimc.netminecraft.packet.PacketTypes; + +/** + * Copyright (c) 2024 lax1dude. All Rights Reserved. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +public class VoiceSignalPackets { + + static final int VOICE_SIGNAL_ALLOWED = 0; + static final int VOICE_SIGNAL_REQUEST = 0; + static final int VOICE_SIGNAL_CONNECT = 1; + static final int VOICE_SIGNAL_DISCONNECT = 2; + static final int VOICE_SIGNAL_ICE = 3; + static final int VOICE_SIGNAL_DESC = 4; + static final int VOICE_SIGNAL_GLOBAL = 5; + + public static void processPacket(byte[] data, ChannelHandlerContext sender) throws IOException { + int packetId = -1; + if(data.length == 0) { + throw new IOException("Zero-length packet recieved"); + } + try { + ByteBuf buffer = Unpooled.wrappedBuffer(data).writerIndex(data.length); + packetId = buffer.readUnsignedByte(); + switch(packetId) { + case VOICE_SIGNAL_REQUEST: { + EaglerVoiceHandler.voiceService.handleVoiceSignalPacketTypeRequest(PacketTypes.readUuid(buffer), sender); + break; + } + case VOICE_SIGNAL_CONNECT: { + EaglerVoiceHandler.voiceService.handleVoiceSignalPacketTypeConnect(sender); + break; + } + case VOICE_SIGNAL_ICE: { + EaglerVoiceHandler.voiceService.handleVoiceSignalPacketTypeICE(PacketTypes.readUuid(buffer), PacketTypes.readString(buffer, 32767), sender); + break; + } + case VOICE_SIGNAL_DESC: { + EaglerVoiceHandler.voiceService.handleVoiceSignalPacketTypeDesc(PacketTypes.readUuid(buffer), PacketTypes.readString(buffer, 32767), sender); + break; + } + case VOICE_SIGNAL_DISCONNECT: { + EaglerVoiceHandler.voiceService.handleVoiceSignalPacketTypeDisconnect(buffer.readableBytes() > 0 ? PacketTypes.readUuid(buffer) : null, sender); + break; + } + default: { + throw new IOException("Unknown packet type " + packetId); + } + } + if(buffer.readableBytes() > 0) { + throw new IOException("Voice packet is too long!"); + } + }catch(IOException ex) { + throw ex; + }catch(Throwable t) { + throw new IOException("Unhandled exception handling voice packet type " + packetId, t); + } + } + + static byte[] makeVoiceSignalPacketAllowed(boolean allowed, String[] iceServers) { + if (iceServers == null) { + byte[] ret = new byte[2]; + ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0); + wrappedBuffer.writeByte(VOICE_SIGNAL_ALLOWED); + wrappedBuffer.writeBoolean(allowed); + return ret; + } + byte[][] iceServersBytes = new byte[iceServers.length][]; + int totalLen = 2 + getVarIntSize(iceServers.length); + for(int i = 0; i < iceServers.length; ++i) { + byte[] b = iceServersBytes[i] = iceServers[i].getBytes(StandardCharsets.UTF_8); + totalLen += getVarIntSize(b.length) + b.length; + } + byte[] ret = new byte[totalLen]; + ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0); + wrappedBuffer.writeByte(VOICE_SIGNAL_ALLOWED); + wrappedBuffer.writeBoolean(allowed); + PacketTypes.writeVarInt(wrappedBuffer, iceServersBytes.length); + for(int i = 0; i < iceServersBytes.length; ++i) { + byte[] b = iceServersBytes[i]; + PacketTypes.writeVarInt(wrappedBuffer, b.length); + wrappedBuffer.writeBytes(b); + } + return ret; + } + + static byte[] makeVoiceSignalPacketGlobal(Collection users) { + int cnt = users.size(); + byte[][] displayNames = new byte[cnt][]; + int i = 0; + for(ChannelHandlerContext user : users) { + String name = ((EaglerVoiceHandler) user.pipeline().get("ayun-eag-voice")).user; + if(name.length() > 16) name = name.substring(0, 16); + displayNames[i++] = name.getBytes(StandardCharsets.UTF_8); + } + int totalLength = 1 + getVarIntSize(cnt) + (cnt << 4); + for(i = 0; i < cnt; ++i) { + totalLength += getVarIntSize(displayNames[i].length) + displayNames[i].length; + } + byte[] ret = new byte[totalLength]; + ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0); + wrappedBuffer.writeByte(VOICE_SIGNAL_GLOBAL); + PacketTypes.writeVarInt(wrappedBuffer, cnt); + for(ChannelHandlerContext user : users) { + PacketTypes.writeUuid(wrappedBuffer, ((EaglerVoiceHandler) user.pipeline().get("ayun-eag-voice")).uuid); + } + for(i = 0; i < cnt; ++i) { + PacketTypes.writeVarInt(wrappedBuffer, displayNames[i].length); + wrappedBuffer.writeBytes(displayNames[i]); + } + return ret; + } + + static byte[] makeVoiceSignalPacketConnect(UUID player, boolean offer) { + byte[] ret = new byte[18]; + ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0); + wrappedBuffer.writeByte(VOICE_SIGNAL_CONNECT); + PacketTypes.writeUuid(wrappedBuffer, player); + wrappedBuffer.writeBoolean(offer); + return ret; + } + + static byte[] makeVoiceSignalPacketDisconnect(UUID player) { + if(player == null) { + return new byte[] { (byte)VOICE_SIGNAL_DISCONNECT }; + } + byte[] ret = new byte[17]; + ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0); + wrappedBuffer.writeByte(VOICE_SIGNAL_DISCONNECT); + PacketTypes.writeUuid(wrappedBuffer, player); + return ret; + } + + static byte[] makeVoiceSignalPacketICE(UUID player, String str) { + byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); + byte[] ret = new byte[17 + getVarIntSize(strBytes.length) + strBytes.length]; + ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0); + wrappedBuffer.writeByte(VOICE_SIGNAL_ICE); + PacketTypes.writeUuid(wrappedBuffer, player); + PacketTypes.writeVarInt(wrappedBuffer, strBytes.length); + wrappedBuffer.writeBytes(strBytes); + return ret; + } + + static byte[] makeVoiceSignalPacketDesc(UUID player, String str) { + byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); + byte[] ret = new byte[17 + getVarIntSize(strBytes.length) + strBytes.length]; + ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0); + wrappedBuffer.writeByte(VOICE_SIGNAL_DESC); + PacketTypes.writeUuid(wrappedBuffer, player); + PacketTypes.writeVarInt(wrappedBuffer, strBytes.length); + wrappedBuffer.writeBytes(strBytes); + return ret; + } + + public static int getVarIntSize(int input) { + for (int i = 1; i < 5; ++i) { + if ((input & -1 << i * 7) == 0) { + return i; + } + } + + return 5; + } +}