diff --git a/gateway/EaglercraftXBungee/EaglerXBungee-Latest.jar b/gateway/EaglercraftXBungee/EaglerXBungee-Latest.jar index 03e93c0..ce1218f 100644 Binary files a/gateway/EaglercraftXBungee/EaglerXBungee-Latest.jar and b/gateway/EaglercraftXBungee/EaglerXBungee-Latest.jar differ diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/EaglerXBungee.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/EaglerXBungee.java index 70a573f..a286ca1 100644 --- a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/EaglerXBungee.java +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/EaglerXBungee.java @@ -30,9 +30,11 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerPipe import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.shit.CompatWarning; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.BinaryHttpClient; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.CapeServiceOffline; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.ISkinService; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinServiceOffline; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.netty.PipelineUtils; @@ -55,9 +57,8 @@ import net.md_5.bungee.BungeeCord; */ public class EaglerXBungee extends Plugin { - public static final String NATIVE_BUNGEECORD_BUILD = "1.20-R0.3-SNAPSHOT:eda268b:1797"; - public static final String NATIVE_WATERFALL_BUILD = "1.20-R0.2-SNAPSHOT:92b5149:562"; - public static final String NATIVE_FLAMECORD_BUILD = "1.1.1"; + public static final String NATIVE_BUNGEECORD_BUILD = "1.20-R0.3-SNAPSHOT:67c65e0:1828"; + public static final String NATIVE_WATERFALL_BUILD = "1.20-R0.3-SNAPSHOT:da6aaf6:572"; static { CompatWarning.displayCompatWarning(); @@ -72,6 +73,8 @@ public class EaglerXBungee extends Plugin { private Timer authServiceTasks = null; private final ChannelFutureListener newChannelListener; private ISkinService skinService; + private CapeServiceOffline capeService; + private VoiceService voiceService; private DefaultAuthSystem defaultAuthSystem; public EaglerXBungee() { @@ -128,7 +131,10 @@ public class EaglerXBungee extends Plugin { } } getProxy().registerChannel(SkinService.CHANNEL); + getProxy().registerChannel(CapeServiceOffline.CHANNEL); getProxy().registerChannel(EaglerPipeline.UPDATE_CERT_CHANNEL); + getProxy().registerChannel(VoiceService.CHANNEL); + getProxy().registerChannel(EaglerPacketEventListener.FNAW_SKIN_ENABLE_CHANNEL); startListeners(); if(skinServiceTasks != null) { skinServiceTasks.cancel(); @@ -165,6 +171,7 @@ public class EaglerXBungee extends Plugin { } }, 1000l, 1000l); } + capeService = new CapeServiceOffline(); if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) { try { defaultAuthSystem = DefaultAuthSystem.initializeAuthSystem(authConf); @@ -185,6 +192,12 @@ public class EaglerXBungee extends Plugin { }, 60000l, 60000l); } } + if(conf.getEnableVoiceChat()) { + voiceService = new VoiceService(conf); + logger().warning("Voice chat enabled, not recommended for public servers!"); + }else { + logger().info("Voice chat disabled, add \"allow_voice: true\" to your listeners to enable"); + } } @Override @@ -193,13 +206,19 @@ public class EaglerXBungee extends Plugin { mgr.unregisterListeners(this); mgr.unregisterCommands(this); getProxy().unregisterChannel(SkinService.CHANNEL); + getProxy().unregisterChannel(CapeServiceOffline.CHANNEL); getProxy().unregisterChannel(EaglerPipeline.UPDATE_CERT_CHANNEL); + getProxy().unregisterChannel(VoiceService.CHANNEL); + getProxy().unregisterChannel(EaglerPacketEventListener.FNAW_SKIN_ENABLE_CHANNEL); stopListeners(); if(skinServiceTasks != null) { skinServiceTasks.cancel(); skinServiceTasks = null; } skinService.shutdown(); + skinService = null; + capeService.shutdown(); + capeService = null; if(defaultAuthSystem != null) { defaultAuthSystem.destroy(); defaultAuthSystem = null; @@ -208,6 +227,7 @@ public class EaglerXBungee extends Plugin { authServiceTasks = null; } } + voiceService = null; BinaryHttpClient.killEventLoop(); } @@ -278,10 +298,18 @@ public class EaglerXBungee extends Plugin { return skinService; } + public CapeServiceOffline getCapeService() { + return capeService; + } + public DefaultAuthSystem getAuthService() { return defaultAuthSystem; } + public VoiceService getVoiceService() { + return voiceService; + } + public static EaglerXBungee getEagler() { return instance; } diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/EaglerBungeeConfig.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/EaglerBungeeConfig.java index 71d51c5..b4cf887 100644 --- a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/EaglerBungeeConfig.java +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/EaglerBungeeConfig.java @@ -9,12 +9,14 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.UUID; import com.google.gson.JsonArray; @@ -31,7 +33,7 @@ import net.md_5.bungee.config.YamlConfiguration; import net.md_5.bungee.protocol.Property; /** - * Copyright (c) 2022-2023 lax1dude. All Rights Reserved. + * Copyright (c) 2022-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 @@ -84,12 +86,14 @@ public class EaglerBungeeConfig { Configuration listenerYml = prov.load(getConfigFile(directory, "listeners.yml")); Iterator listeners = listenerYml.getKeys().iterator(); Map serverListeners = new HashMap(); + boolean voiceChat = false; while(listeners.hasNext()) { String name = listeners.next(); EaglerListenerConfig conf = EaglerListenerConfig.loadConfig(listenerYml.getSection(name), contentTypes); if(conf != null) { serverListeners.put(name, conf); + voiceChat |= conf.getEnableVoiceChat(); }else { EaglerXBungee.logger().severe("Invalid listener config: " + name); } @@ -105,6 +109,9 @@ public class EaglerBungeeConfig { Configuration updatesYml = prov.load(getConfigFile(directory, "updates.yml")); EaglerUpdateConfig updatesConfig = EaglerUpdateConfig.loadConfig(updatesYml); + Configuration iceServersYml = prov.load(getConfigFile(directory, "ice_servers.yml")); + Collection iceServers = loadICEServers(iceServersYml); + if(authConfig.isEnableAuthentication()) { for(EaglerListenerConfig lst : serverListeners.values()) { if(lst.getRatelimitLogin() != null) lst.getRatelimitLogin().setDivisor(2); @@ -135,12 +142,18 @@ public class EaglerBungeeConfig { if(eaglerPlayersVanillaSkin != null && eaglerPlayersVanillaSkin.length() == 0) { eaglerPlayersVanillaSkin = null; } + boolean enableIsEaglerPlayerProperty = configYml.getBoolean("enable_is_eagler_player_property", true); + Set disableVoiceOnServers = new HashSet((Collection)configYml.getList("disable_voice_chat_on_servers")); + boolean disableFNAWSkinsEverywhere = configYml.getBoolean("disable_fnaw_skins_everywhere", false); + Set disableFNAWSkinsOnServers = new HashSet((Collection)configYml.getList("disable_fnaw_skins_on_servers")); final EaglerBungeeConfig ret = new EaglerBungeeConfig(serverName, serverUUID, websocketKeepAliveTimeout, websocketHandshakeTimeout, websocketCompressionLevel, serverListeners, contentTypes, downloadVanillaSkins, validSkinUrls, uuidRateLimitPlayer, uuidRateLimitGlobal, skinRateLimitPlayer, skinRateLimitGlobal, skinCacheURI, keepObjectsDays, keepProfilesDays, maxObjects, maxProfiles, - antagonistsRateLimit, sqliteDriverClass, sqliteDriverPath, eaglerPlayersVanillaSkin, authConfig, updatesConfig); + antagonistsRateLimit, sqliteDriverClass, sqliteDriverPath, eaglerPlayersVanillaSkin, + enableIsEaglerPlayerProperty, authConfig, updatesConfig, iceServers, voiceChat, + disableVoiceOnServers, disableFNAWSkinsEverywhere, disableFNAWSkinsOnServers); if(eaglerPlayersVanillaSkin != null) { VanillaDefaultSkinProfileLoader.lookupVanillaSkinUser(ret); @@ -202,6 +215,18 @@ public class EaglerBungeeConfig { } } + private static Collection loadICEServers(Configuration config) { + Collection ret = new ArrayList(config.getList("voice_stun_servers")); + Configuration turnServers = config.getSection("voice_turn_servers"); + Iterator turnItr = turnServers.getKeys().iterator(); + while(turnItr.hasNext()) { + String name = turnItr.next(); + Configuration turnSvr = turnServers.getSection(name); + ret.add(turnSvr.getString("url") + ";" + turnSvr.getString("username") + ";" + turnSvr.getString("password")); + } + return ret; + } + @SuppressWarnings("deprecation") private static JsonObject parseJsonObject(InputStream file) throws IOException { StringBuilder str = new StringBuilder(); @@ -243,8 +268,14 @@ public class EaglerBungeeConfig { private final String sqliteDriverClass; private final String sqliteDriverPath; private final String eaglerPlayersVanillaSkin; + private final boolean enableIsEaglerPlayerProperty; private final EaglerAuthConfig authConfig; private final EaglerUpdateConfig updateConfig; + private final Collection iceServers; + private final boolean enableVoiceChat; + private final Set disableVoiceOnServers; + private final boolean disableFNAWSkinsEverywhere; + private final Set disableFNAWSkinsOnServers; Property[] eaglerPlayersVanillaSkinCached = new Property[] { isEaglerProperty }; public String getServerName() { @@ -362,6 +393,10 @@ public class EaglerBungeeConfig { return eaglerPlayersVanillaSkin; } + public boolean getEnableIsEaglerPlayerProperty() { + return enableIsEaglerPlayerProperty; + } + public Property[] getEaglerPlayersVanillaSkinProperties() { return eaglerPlayersVanillaSkinCached; } @@ -378,6 +413,26 @@ public class EaglerBungeeConfig { return updateConfig; } + public Collection getICEServers() { + return iceServers; + } + + public boolean getEnableVoiceChat() { + return enableVoiceChat; + } + + public Set getDisableVoiceOnServersSet() { + return disableVoiceOnServers; + } + + public boolean getDisableFNAWSkinsEverywhere() { + return disableFNAWSkinsEverywhere; + } + + public Set getDisableFNAWSkinsOnServersSet() { + return disableFNAWSkinsOnServers; + } + private EaglerBungeeConfig(String serverName, UUID serverUUID, long websocketKeepAliveTimeout, long websocketHandshakeTimeout, int httpWebsocketCompressionLevel, Map serverListeners, Map contentTypes, @@ -385,7 +440,9 @@ public class EaglerBungeeConfig { int uuidRateLimitGlobal, int skinRateLimitPlayer, int skinRateLimitGlobal, String skinCacheURI, int keepObjectsDays, int keepProfilesDays, int maxObjects, int maxProfiles, int antagonistsRateLimit, String sqliteDriverClass, String sqliteDriverPath, String eaglerPlayersVanillaSkin, - EaglerAuthConfig authConfig, EaglerUpdateConfig updateConfig) { + boolean enableIsEaglerPlayerProperty, EaglerAuthConfig authConfig, EaglerUpdateConfig updateConfig, + Collection iceServers, boolean enableVoiceChat, Set disableVoiceOnServers, + boolean disableFNAWSkinsEverywhere, Set disableFNAWSkinsOnServers) { this.serverName = serverName; this.serverUUID = serverUUID; this.serverListeners = serverListeners; @@ -408,8 +465,14 @@ public class EaglerBungeeConfig { this.sqliteDriverClass = sqliteDriverClass; this.sqliteDriverPath = sqliteDriverPath; this.eaglerPlayersVanillaSkin = eaglerPlayersVanillaSkin; + this.enableIsEaglerPlayerProperty = enableIsEaglerPlayerProperty; this.authConfig = authConfig; this.updateConfig = updateConfig; + this.iceServers = iceServers; + this.enableVoiceChat = enableVoiceChat; + this.disableVoiceOnServers = disableVoiceOnServers; + this.disableFNAWSkinsEverywhere = disableFNAWSkinsEverywhere; + this.disableFNAWSkinsOnServers = disableFNAWSkinsOnServers; } } diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/EaglerListenerConfig.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/EaglerListenerConfig.java index e647c36..de2e56e 100644 --- a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/EaglerListenerConfig.java +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/EaglerListenerConfig.java @@ -120,6 +120,8 @@ public class EaglerListenerConfig extends ListenerInfo { contentTypes, indexPage, page404); } + boolean enableVoiceChat = config.getBoolean("allow_voice", false); + EaglerRateLimiter ratelimitIp = null; EaglerRateLimiter ratelimitLogin = null; EaglerRateLimiter ratelimitMOTD = null; @@ -149,7 +151,7 @@ public class EaglerListenerConfig extends ListenerInfo { cacheTrending, cachePortfolios); return new EaglerListenerConfig(hostv4, hostv6, maxPlayer, tabListType, defaultServer, forceDefaultServer, forwardIp, forwardIpHeader, redirectLegacyClientsTo, serverIcon, serverMOTD, allowMOTD, allowQuery, - cacheConfig, httpServer, ratelimitIp, ratelimitLogin, ratelimitMOTD, ratelimitQuery); + cacheConfig, httpServer, enableVoiceChat, ratelimitIp, ratelimitLogin, ratelimitMOTD, ratelimitQuery); } private final InetSocketAddress address; @@ -169,6 +171,7 @@ public class EaglerListenerConfig extends ListenerInfo { private final HttpWebServer webServer; private boolean serverIconSet = false; private int[] serverIconPixels = null; + private final boolean enableVoiceChat; private final EaglerRateLimiter ratelimitIp; private final EaglerRateLimiter ratelimitLogin; private final EaglerRateLimiter ratelimitMOTD; @@ -178,8 +181,8 @@ public class EaglerListenerConfig extends ListenerInfo { String tabListType, String defaultServer, boolean forceDefaultServer, boolean forwardIp, String forwardIpHeader, String redirectLegacyClientsTo, String serverIcon, List serverMOTD, boolean allowMOTD, boolean allowQuery, MOTDCacheConfiguration motdCacheConfig, HttpWebServer webServer, - EaglerRateLimiter ratelimitIp, EaglerRateLimiter ratelimitLogin, EaglerRateLimiter ratelimitMOTD, - EaglerRateLimiter ratelimitQuery) { + boolean enableVoiceChat, EaglerRateLimiter ratelimitIp, EaglerRateLimiter ratelimitLogin, + EaglerRateLimiter ratelimitMOTD, EaglerRateLimiter ratelimitQuery) { super(address, String.join("\n", serverMOTD), maxPlayer, 60, Arrays.asList(defaultServer), forceDefaultServer, Collections.emptyMap(), tabListType, false, false, 0, false, false); this.address = address; @@ -197,6 +200,7 @@ public class EaglerListenerConfig extends ListenerInfo { this.allowQuery = allowQuery; this.motdCacheConfig = motdCacheConfig; this.webServer = webServer; + this.enableVoiceChat = enableVoiceChat; this.ratelimitIp = ratelimitIp; this.ratelimitLogin = ratelimitLogin; this.ratelimitMOTD = ratelimitMOTD; @@ -285,6 +289,10 @@ public class EaglerListenerConfig extends ListenerInfo { return redirectLegacyClientsTo; } + public boolean getEnableVoiceChat() { + return enableVoiceChat; + } + public EaglerRateLimiter getRatelimitIp() { return ratelimitIp; } diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/handlers/EaglerPacketEventListener.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/handlers/EaglerPacketEventListener.java index 5bf9b0c..fcd2740 100644 --- a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/handlers/EaglerPacketEventListener.java +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/handlers/EaglerPacketEventListener.java @@ -2,6 +2,7 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.UUID; import java.util.logging.Level; import org.apache.commons.codec.binary.Base64; @@ -14,16 +15,23 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.DefaultAuthSystem; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerAuthConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.CapePackets; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.CapeServiceOffline; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinPackets; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceSignalPackets; import net.md_5.bungee.UserConnection; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PluginMessageEvent; import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.api.event.ServerConnectedEvent; +import net.md_5.bungee.api.event.ServerDisconnectEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.connection.LoginResult; @@ -47,6 +55,8 @@ import net.md_5.bungee.protocol.Property; */ public class EaglerPacketEventListener implements Listener { + public static final String FNAW_SKIN_ENABLE_CHANNEL = "EAG|FNAWSEn-1.8"; + public final EaglerXBungee plugin; public EaglerPacketEventListener(EaglerXBungee plugin) { @@ -56,28 +66,46 @@ public class EaglerPacketEventListener implements Listener { @EventHandler public void onPluginMessage(final PluginMessageEvent event) { if(event.getSender() instanceof UserConnection) { - if(SkinService.CHANNEL.equals(event.getTag())) { - event.setCancelled(true); - final UserConnection sender = (UserConnection)event.getSender(); - if(sender.getPendingConnection() instanceof EaglerInitialHandler) { + final UserConnection player = (UserConnection)event.getSender(); + if(player.getPendingConnection() instanceof EaglerInitialHandler) { + if(SkinService.CHANNEL.equals(event.getTag())) { + event.setCancelled(true); ProxyServer.getInstance().getScheduler().runAsync(plugin, new Runnable() { @Override public void run() { try { - SkinPackets.processPacket(event.getData(), sender, plugin.getSkinService()); + SkinPackets.processPacket(event.getData(), player, plugin.getSkinService()); } catch (IOException e) { event.getSender().disconnect(new TextComponent("Skin packet error!")); - EaglerXBungee.logger().log(Level.SEVERE, "Eagler user \"" + sender.getName() + "\" raised an exception handling skins!", e); + EaglerXBungee.logger().log(Level.SEVERE, "Eagler user \"" + player.getName() + "\" raised an exception handling skins!", e); } } }); - }else { - event.getSender().disconnect(new TextComponent("Cannot send \"" + SkinService.CHANNEL + "\" on a non-eagler connection!")); + }else if(CapeServiceOffline.CHANNEL.equals(event.getTag())) { + event.setCancelled(true); + try { + CapePackets.processPacket(event.getData(), player, plugin.getCapeService()); + } catch (IOException e) { + event.getSender().disconnect(new TextComponent("Cape packet error!")); + EaglerXBungee.logger().log(Level.SEVERE, "Eagler user \"" + player.getName() + "\" raised an exception handling capes!", e); + } + }else if(VoiceService.CHANNEL.equals(event.getTag())) { + event.setCancelled(true); + VoiceService svc = plugin.getVoiceService(); + if(svc != null && ((EaglerInitialHandler)player.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) { + try { + VoiceSignalPackets.processPacket(event.getData(), player, svc); + } catch (IOException e) { + event.getSender().disconnect(new TextComponent("Voice signal packet error!")); + EaglerXBungee.logger().log(Level.SEVERE, "Eagler user \"" + player.getName() + "\" raised an exception handling voice signals!", e); + } + } } } }else if(event.getSender() instanceof Server && event.getReceiver() instanceof UserConnection) { UserConnection player = (UserConnection)event.getReceiver(); if("EAG|GetDomain".equals(event.getTag()) && player.getPendingConnection() instanceof EaglerInitialHandler) { + event.setCancelled(true); String domain = ((EaglerInitialHandler)player.getPendingConnection()).getOrigin(); if(domain == null) { ((Server)event.getSender()).sendData("EAG|Domain", new byte[] { 0 }); @@ -119,9 +147,9 @@ public class EaglerPacketEventListener implements Listener { } } } - EaglerAuthConfig authConf = EaglerXBungee.getEagler().getConfig().getAuthConfig(); + EaglerAuthConfig authConf = plugin.getConfig().getAuthConfig(); if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) { - DefaultAuthSystem srv = EaglerXBungee.getEagler().getAuthService(); + DefaultAuthSystem srv = plugin.getAuthService(); if(srv != null) { srv.handleVanillaLogin(event); } @@ -131,6 +159,47 @@ public class EaglerPacketEventListener implements Listener { @EventHandler public void onConnectionLost(PlayerDisconnectEvent event) { - plugin.getSkinService().unregisterPlayer(event.getPlayer().getUniqueId()); + UUID uuid = event.getPlayer().getUniqueId(); + plugin.getSkinService().unregisterPlayer(uuid); + plugin.getCapeService().unregisterPlayer(uuid); + if(event.getPlayer() instanceof UserConnection) { + UserConnection player = (UserConnection)event.getPlayer(); + if((player.getPendingConnection() instanceof EaglerInitialHandler) + && ((EaglerInitialHandler) player.getPendingConnection()).getEaglerListenerConfig() + .getEnableVoiceChat()) { + plugin.getVoiceService().handlePlayerLoggedOut(player); + } + } + } + + @EventHandler + public void onServerConnected(ServerConnectedEvent event) { + if(event.getPlayer() instanceof UserConnection) { + UserConnection player = (UserConnection)event.getPlayer(); + if(player.getPendingConnection() instanceof EaglerInitialHandler) { + EaglerInitialHandler handler = (EaglerInitialHandler) player.getPendingConnection(); + ServerInfo sv = event.getServer().getInfo(); + boolean fnawSkins = !plugin.getConfig().getDisableFNAWSkinsEverywhere() && !plugin.getConfig().getDisableFNAWSkinsOnServersSet().contains(sv.getName()); + if(fnawSkins != handler.currentFNAWSkinEnableStatus) { + handler.currentFNAWSkinEnableStatus = fnawSkins; + player.sendData(FNAW_SKIN_ENABLE_CHANNEL, new byte[] { fnawSkins ? (byte)1 : (byte)0 }); + } + if(handler.getEaglerListenerConfig().getEnableVoiceChat()) { + plugin.getVoiceService().handleServerConnected(player, sv); + } + } + } + } + + @EventHandler + public void onServerDisconnected(ServerDisconnectEvent event) { + if(event.getPlayer() instanceof UserConnection) { + UserConnection player = (UserConnection)event.getPlayer(); + if((player.getPendingConnection() instanceof EaglerInitialHandler) + && ((EaglerInitialHandler) player.getPendingConnection()).getEaglerListenerConfig() + .getEnableVoiceChat()) { + plugin.getVoiceService().handleServerDisconnected(player, event.getTarget()); + } + } } } diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/server/EaglerInitialHandler.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/server/EaglerInitialHandler.java index 2cecf1b..c7377d0 100644 --- a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/server/EaglerInitialHandler.java +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/server/EaglerInitialHandler.java @@ -10,11 +10,12 @@ import java.util.UUID; import gnu.trove.set.TIntSet; import gnu.trove.set.hash.TIntHashSet; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SimpleRateLimiter; import net.md_5.bungee.BungeeCord; import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.connection.LoginResult; import net.md_5.bungee.netty.ChannelWrapper; @@ -67,12 +68,17 @@ public class EaglerInitialHandler extends InitialHandler { public final SimpleRateLimiter skinLookupRateLimiter; public final SimpleRateLimiter skinUUIDLookupRateLimiter; public final SimpleRateLimiter skinTextureDownloadRateLimiter; + public final SimpleRateLimiter capeLookupRateLimiter; + public final SimpleRateLimiter voiceConnectRateLimiter; public final String origin; public final ClientCertificateHolder clientCertificate; public final Set certificatesToSend; public final TIntSet certificatesSent; + public boolean currentFNAWSkinEnableStatus = true; - public EaglerInitialHandler(BungeeCord bungee, ListenerInfo listener, final ChannelWrapper ch, + private static final Property[] NO_PROPERTIES = new Property[0]; + + public EaglerInitialHandler(BungeeCord bungee, EaglerListenerConfig listener, final ChannelWrapper ch, int gameProtocolVersion, String username, UUID playerUUID, InetSocketAddress address, String host, String origin, ClientCertificateHolder clientCertificate) { super(bungee, listener); @@ -84,6 +90,8 @@ public class EaglerInitialHandler extends InitialHandler { this.skinLookupRateLimiter = new SimpleRateLimiter(); this.skinUUIDLookupRateLimiter = new SimpleRateLimiter(); this.skinTextureDownloadRateLimiter = new SimpleRateLimiter(); + this.capeLookupRateLimiter = new SimpleRateLimiter(); + this.voiceConnectRateLimiter = new SimpleRateLimiter(); this.clientCertificate = clientCertificate; this.certificatesToSend = new HashSet(); this.certificatesSent = new TIntHashSet(); @@ -108,7 +116,11 @@ public class EaglerInitialHandler extends InitialHandler { ch.getHandle().writeAndFlush(arg0); } }; - setLoginProfile(new LoginResult(playerUUID.toString(), username, new Property[] { EaglerBungeeConfig.isEaglerProperty })); + Property[] profileProperties = NO_PROPERTIES; + if(EaglerXBungee.getEagler().getConfig().getEnableIsEaglerPlayerProperty()) { + profileProperties = new Property[] { EaglerBungeeConfig.isEaglerProperty }; + } + setLoginProfile(new LoginResult(playerUUID.toString(), username, profileProperties)); try { super.connected(ch); } catch (Exception e) { @@ -251,4 +263,7 @@ public class EaglerInitialHandler extends InitialHandler { return origin; } + public EaglerListenerConfig getEaglerListenerConfig() { + return (EaglerListenerConfig)getListener(); + } } diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/server/HttpWebSocketHandler.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/server/HttpWebSocketHandler.java index bcc885d..aa27e95 100644 --- a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/server/HttpWebSocketHandler.java +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/server/HttpWebSocketHandler.java @@ -56,6 +56,7 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInit import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerBungeeProtocol; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.MOTDQueryHandler; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.QueryManager; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.CapePackets; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinPackets; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService; import net.md_5.bungee.BungeeCord; @@ -949,7 +950,21 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter { } } - bungee.getPluginManager().callEvent(new PostLoginEvent(userCon)); + if(profileData.containsKey("cape_v1")) { + try { + CapePackets.registerEaglerPlayer(clientUUID, profileData.get("cape_v1"), + EaglerXBungee.getEagler().getCapeService()); + } catch (Throwable ex) { + CapePackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getCapeService()); + EaglerXBungee.logger().info("[" + ctx.channel().remoteAddress() + "]: Invalid cape packet: " + ex.toString()); + } + }else { + CapePackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getCapeService()); + } + + if(conf.getEnableVoiceChat()) { + EaglerXBungee.getEagler().getVoiceService().handlePlayerLoggedIn(userCon); + } ServerInfo server; if (bungee.getReconnectHandler() != null) { @@ -957,13 +972,30 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter { } else { server = AbstractReconnectHandler.getForcedHost(initialHandler); } - + if (server == null) { server = bungee.getServerInfo(conf.getDefaultServer()); } - eaglerCon.hasBeenForwarded = true; - userCon.connect(server, null, true, ServerConnectEvent.Reason.JOIN_PROXY); + final ServerInfo server2 = server; + Callback complete = new Callback() { + @Override + public void done(PostLoginEvent result, Throwable error) { + eaglerCon.hasBeenForwarded = true; + if (ch.isClosed()) { + return; + } + userCon.connect(server2, null, true, ServerConnectEvent.Reason.JOIN_PROXY); + } + }; + + try { + PostLoginEvent login = new PostLoginEvent(userCon); + bungee.getPluginManager().callEvent(login); + complete.done(login, null); + }catch(NoSuchMethodError err) { + bungee.getPluginManager().callEvent(PostLoginEvent.class.getDeclaredConstructor(ProxiedPlayer.class, ServerInfo.class, Callback.class).newInstance(userCon, server, complete)); + } } }); diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/shit/CompatWarning.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/shit/CompatWarning.java index e0bdbfb..2cdff34 100644 --- a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/shit/CompatWarning.java +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/shit/CompatWarning.java @@ -38,7 +38,6 @@ public class CompatWarning { ":> ", ":> - BungeeCord: " + EaglerXBungee.NATIVE_BUNGEECORD_BUILD, ":> - Waterfall: " + EaglerXBungee.NATIVE_WATERFALL_BUILD, - ":> - FlameCord: " + EaglerXBungee.NATIVE_FLAMECORD_BUILD, ":> ", ":> This is not a Bukkit/Spigot plugin!", ":> ", diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/AsyncSkinProvider.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/AsyncSkinProvider.java index ecbdad6..17932fe 100644 --- a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/AsyncSkinProvider.java +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/AsyncSkinProvider.java @@ -108,7 +108,7 @@ public class AsyncSkinProvider { byte[] loadedPixels = new byte[16384]; image.getRGB(0, 0, 64, 64, tmp, 0, 64); SkinRescaler.convertToBytes(tmp, loadedPixels); - SkinPackets.setAlphaForChest(loadedPixels, (byte)255); + SkinPackets.setAlphaForChest(loadedPixels, (byte)255, 0); doAccept(loadedPixels); return; }else if(srcWidth == 64 && srcHeight == 32) { @@ -116,7 +116,7 @@ public class AsyncSkinProvider { byte[] loadedPixels = new byte[16384]; image.getRGB(0, 0, 64, 32, tmp1, 0, 64); SkinRescaler.convert64x32To64x64(tmp1, loadedPixels); - SkinPackets.setAlphaForChest(loadedPixels, (byte)255); + SkinPackets.setAlphaForChest(loadedPixels, (byte)255, 0); doAccept(loadedPixels); return; }else { diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/CapePackets.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/CapePackets.java new file mode 100644 index 0000000..4828c7b --- /dev/null +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/CapePackets.java @@ -0,0 +1,110 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins; + +import java.io.IOException; +import java.util.UUID; + +import net.md_5.bungee.UserConnection; + +/** + * 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 CapePackets { + + public static final int PACKET_MY_CAPE_PRESET = 0x01; + public static final int PACKET_MY_CAPE_CUSTOM = 0x02; + public static final int PACKET_GET_OTHER_CAPE = 0x03; + public static final int PACKET_OTHER_CAPE_PRESET = 0x04; + public static final int PACKET_OTHER_CAPE_CUSTOM = 0x05; + + public static void processPacket(byte[] data, UserConnection sender, CapeServiceOffline capeService) throws IOException { + if(data.length == 0) { + throw new IOException("Zero-length packet recieved"); + } + int packetId = (int)data[0] & 0xFF; + try { + switch(packetId) { + case PACKET_GET_OTHER_CAPE: + processGetOtherCape(data, sender, capeService); + break; + default: + throw new IOException("Unknown packet type " + packetId); + } + }catch(IOException ex) { + throw ex; + }catch(Throwable t) { + throw new IOException("Unhandled exception handling cape packet type " + packetId, t); + } + } + + private static void processGetOtherCape(byte[] data, UserConnection sender, CapeServiceOffline capeService) throws IOException { + if(data.length != 17) { + throw new IOException("Invalid length " + data.length + " for skin request packet"); + } + UUID searchUUID = SkinPackets.bytesToUUID(data, 1); + capeService.processGetOtherCape(searchUUID, sender); + } + + public static void registerEaglerPlayer(UUID clientUUID, byte[] bs, CapeServiceOffline capeService) throws IOException { + if(bs.length == 0) { + throw new IOException("Zero-length packet recieved"); + } + byte[] generatedPacket; + int packetType = (int)bs[0] & 0xFF; + switch(packetType) { + case PACKET_MY_CAPE_PRESET: + if(bs.length != 5) { + throw new IOException("Invalid length " + bs.length + " for preset cape packet"); + } + generatedPacket = CapePackets.makePresetResponse(clientUUID, (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF)); + break; + case PACKET_MY_CAPE_CUSTOM: + if(bs.length != 1174) { + throw new IOException("Invalid length " + bs.length + " for custom cape packet"); + } + generatedPacket = CapePackets.makeCustomResponse(clientUUID, bs, 1, 1173); + break; + default: + throw new IOException("Unknown skin packet type: " + packetType); + } + capeService.registerEaglercraftPlayer(clientUUID, generatedPacket); + } + + public static void registerEaglerPlayerFallback(UUID clientUUID, CapeServiceOffline capeService) { + capeService.registerEaglercraftPlayer(clientUUID, CapePackets.makePresetResponse(clientUUID, 0)); + } + + public static byte[] makePresetResponse(UUID uuid, int presetId) { + byte[] ret = new byte[1 + 16 + 4]; + ret[0] = (byte)PACKET_OTHER_CAPE_PRESET; + SkinPackets.UUIDToBytes(uuid, ret, 1); + ret[17] = (byte)(presetId >> 24); + ret[18] = (byte)(presetId >> 16); + ret[19] = (byte)(presetId >> 8); + ret[20] = (byte)(presetId & 0xFF); + return ret; + } + + public static byte[] makeCustomResponse(UUID uuid, byte[] pixels) { + return makeCustomResponse(uuid, pixels, 0, pixels.length); + } + + public static byte[] makeCustomResponse(UUID uuid, byte[] pixels, int offset, int length) { + byte[] ret = new byte[1 + 16 + length]; + ret[0] = (byte)PACKET_OTHER_CAPE_CUSTOM; + SkinPackets.UUIDToBytes(uuid, ret, 1); + System.arraycopy(pixels, offset, ret, 17, length); + return ret; + } +} diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/CapeServiceOffline.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/CapeServiceOffline.java new file mode 100644 index 0000000..29fd6d2 --- /dev/null +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/CapeServiceOffline.java @@ -0,0 +1,64 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler; +import net.md_5.bungee.UserConnection; + +/** + * 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 CapeServiceOffline { + + public static final int masterRateLimitPerPlayer = 250; + + public static final String CHANNEL = "EAG|Capes-1.8"; + + private final Map capesCache = new HashMap(); + + public void registerEaglercraftPlayer(UUID playerUUID, byte[] capePacket) { + synchronized(capesCache) { + capesCache.put(playerUUID, capePacket); + } + } + + public void processGetOtherCape(UUID searchUUID, UserConnection sender) { + if(((EaglerInitialHandler)sender.getPendingConnection()).skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) { + byte[] maybeCape; + synchronized(capesCache) { + maybeCape = capesCache.get(searchUUID); + } + if(maybeCape != null) { + sender.sendData(CapeServiceOffline.CHANNEL, maybeCape); + }else { + sender.sendData(CapeServiceOffline.CHANNEL, CapePackets.makePresetResponse(searchUUID, 0)); + } + } + } + + public void unregisterPlayer(UUID playerUUID) { + synchronized(capesCache) { + capesCache.remove(playerUUID); + } + } + + public void shutdown() { + synchronized(capesCache) { + capesCache.clear(); + } + } +} diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/SkinPackets.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/SkinPackets.java index 6a0c4f7..fd8349e 100644 --- a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/SkinPackets.java +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/SkinPackets.java @@ -9,7 +9,7 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee; import net.md_5.bungee.UserConnection; /** - * Copyright (c) 2022-2023 lax1dude. All Rights Reserved. + * Copyright (c) 2022-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 @@ -110,13 +110,11 @@ public class SkinPackets { generatedPacket = SkinPackets.makePresetResponse(clientUUID, (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF)); break; case PACKET_MY_SKIN_CUSTOM: - byte[] pixels = new byte[16384]; - if(bs.length != 2 + pixels.length) { + if(bs.length != 2 + 16384) { throw new IOException("Invalid length " + bs.length + " for custom skin packet"); } - setAlphaForChest(pixels, (byte)255); - System.arraycopy(bs, 2, pixels, 0, pixels.length); - generatedPacket = SkinPackets.makeCustomResponse(clientUUID, (skinModel = (int)bs[1] & 0xFF), pixels); + setAlphaForChest(bs, (byte)255, 2); + generatedPacket = SkinPackets.makeCustomResponse(clientUUID, (skinModel = (int)bs[1] & 0xFF), bs, 2, 16384); break; default: throw new IOException("Unknown skin packet type: " + packetType); @@ -130,13 +128,13 @@ public class SkinPackets { skinService.registerEaglercraftPlayer(clientUUID, generatedPacket, skinModel); } - public static void setAlphaForChest(byte[] skin64x64, byte alpha) { - if(skin64x64.length != 16384) { + public static void setAlphaForChest(byte[] skin64x64, byte alpha, int offset) { + if(skin64x64.length - offset != 16384) { throw new IllegalArgumentException("Skin is not 64x64!"); } for(int y = 20; y < 32; ++y) { for(int x = 16; x < 40; ++x) { - skin64x64[(y << 8) | (x << 2)] = alpha; + skin64x64[offset + ((y << 8) | (x << 2))] = alpha; } } } @@ -157,11 +155,15 @@ public class SkinPackets { } public static byte[] makeCustomResponse(UUID uuid, int model, byte[] pixels) { - byte[] ret = new byte[1 + 16 + 1 + pixels.length]; + return makeCustomResponse(uuid, model, pixels, 0, pixels.length); + } + + public static byte[] makeCustomResponse(UUID uuid, int model, byte[] pixels, int offset, int length) { + byte[] ret = new byte[1 + 16 + 1 + length]; ret[0] = (byte)PACKET_OTHER_SKIN_CUSTOM; UUIDToBytes(uuid, ret, 1); ret[17] = (byte)model; - System.arraycopy(pixels, 0, ret, 18, pixels.length); + System.arraycopy(pixels, offset, ret, 18, length); return ret; } diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/SkinService.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/SkinService.java index 94a1f60..71ffc44 100644 --- a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/SkinService.java +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/skins/SkinService.java @@ -52,12 +52,6 @@ public class SkinService implements ISkinService { public static final int masterRateLimitPerPlayer = 250; - public static final int PACKET_MY_SKIN_PRESET = 0x01; - public static final int PACKET_MY_SKIN_CUSTOM = 0x02; - public static final int PACKET_GET_OTHER_SKIN = 0x03; - public static final int PACKET_OTHER_SKIN_PRESET = 0x04; - public static final int PACKET_OTHER_SKIN_CUSTOM = 0x05; - public static final String CHANNEL = "EAG|Skins-1.8"; private final Map onlinePlayersCache = new HashMap(); diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/ExpiringSet.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/ExpiringSet.java new file mode 100644 index 0000000..547e094 --- /dev/null +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/ExpiringSet.java @@ -0,0 +1,84 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + +/** + * Copyright (c) 2022 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 ExpiringSet extends HashSet { + private final long expiration; + private final ExpiringEvent event; + + private final Map timestamps = new HashMap<>(); + + public ExpiringSet(long expiration) { + this.expiration = expiration; + this.event = null; + } + + public ExpiringSet(long expiration, ExpiringEvent event) { + this.expiration = expiration; + this.event = event; + } + + public interface ExpiringEvent { + void onExpiration(T item); + } + + public void checkForExpirations() { + Iterator iterator = this.timestamps.keySet().iterator(); + long now = System.currentTimeMillis(); + while (iterator.hasNext()) { + T element = iterator.next(); + if (super.contains(element)) { + if (this.timestamps.get(element) + this.expiration < now) { + if (this.event != null) this.event.onExpiration(element); + iterator.remove(); + super.remove(element); + } + } else { + iterator.remove(); + super.remove(element); + } + } + } + + public boolean add(T o) { + checkForExpirations(); + boolean success = super.add(o); + if (success) timestamps.put(o, System.currentTimeMillis()); + return success; + } + + public boolean remove(Object o) { + checkForExpirations(); + boolean success = super.remove(o); + if (success) timestamps.remove(o); + return success; + } + + public void clear() { + this.timestamps.clear(); + super.clear(); + } + + public boolean contains(Object o) { + checkForExpirations(); + return super.contains(o); + } +} diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/VoiceServerImpl.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/VoiceServerImpl.java new file mode 100644 index 0000000..392a09a --- /dev/null +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/VoiceServerImpl.java @@ -0,0 +1,243 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler; +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.api.config.ServerInfo; + +/** + * 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 { + + private final ServerInfo server; + private final byte[] iceServersPacket; + + private final Map voicePlayers = new HashMap<>(); + private final Map> voiceRequests = new HashMap<>(); + private final Set voicePairs = new HashSet<>(); + + private static final int VOICE_CONNECT_RATELIMIT = 15; + + 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(ServerInfo server, byte[] iceServersPacket) { + this.server = server; + this.iceServersPacket = iceServersPacket; + } + + public void handlePlayerLoggedIn(UserConnection player) { + player.sendData(VoiceService.CHANNEL, iceServersPacket); + } + + public void handlePlayerLoggedOut(UserConnection player) { + removeUser(player.getUniqueId()); + } + + void handleVoiceSignalPacketTypeRequest(UUID player, UserConnection sender) { + synchronized (voicePlayers) { + UUID senderUUID = sender.getUniqueId(); + if (senderUUID.equals(player)) + return; // prevent duplicates + if (!voicePlayers.containsKey(senderUUID)) + return; + UserConnection 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); + targetPlayerCon.sendData(VoiceService.CHANNEL, + VoiceSignalPackets.makeVoiceSignalPacketConnect(senderUUID, false)); + sender.sendData(VoiceService.CHANNEL, VoiceSignalPackets.makeVoiceSignalPacketConnect(player, true)); + } + } + } + + void handleVoiceSignalPacketTypeConnect(UserConnection sender) { + if(!((EaglerInitialHandler)sender.getPendingConnection()).voiceConnectRateLimiter.rateLimit(VOICE_CONNECT_RATELIMIT)) { + return; + } + synchronized (voicePlayers) { + if (voicePlayers.containsKey(sender.getUniqueId())) { + return; + } + boolean hasNoOtherPlayers = voicePlayers.isEmpty(); + voicePlayers.put(sender.getUniqueId(), sender); + if (hasNoOtherPlayers) { + return; + } + byte[] packetToBroadcast = VoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values()); + for (UserConnection userCon : voicePlayers.values()) { + userCon.sendData(VoiceService.CHANNEL, packetToBroadcast); + } + } + } + + void handleVoiceSignalPacketTypeICE(UUID player, String str, UserConnection sender) { + UserConnection pass; + VoicePair pair = new VoicePair(player, sender.getUniqueId()); + synchronized (voicePlayers) { + pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null; + } + if (pass != null) { + pass.sendData(VoiceService.CHANNEL, VoiceSignalPackets.makeVoiceSignalPacketICE(sender.getUniqueId(), str)); + } + } + + void handleVoiceSignalPacketTypeDesc(UUID player, String str, UserConnection sender) { + UserConnection pass; + VoicePair pair = new VoicePair(player, sender.getUniqueId()); + synchronized (voicePlayers) { + pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null; + } + if (pass != null) { + pass.sendData(VoiceService.CHANNEL, + VoiceSignalPackets.makeVoiceSignalPacketDesc(sender.getUniqueId(), str)); + } + } + + void handleVoiceSignalPacketTypeDisconnect(UUID player, UserConnection sender) { + 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(); + UserConnection conn = voicePlayers.get(target); + if (conn != null) { + if (userDisconnectPacket == null) { + userDisconnectPacket = VoiceSignalPackets.makeVoiceSignalPacketDisconnect(player); + } + conn.sendData(VoiceService.CHANNEL, userDisconnectPacket); + } + sender.sendData(VoiceService.CHANNEL, + VoiceSignalPackets.makeVoiceSignalPacketDisconnect(target)); + } + } + } + } else { + removeUser(sender.getUniqueId()); + } + } + + 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 (UserConnection userCon : voicePlayers.values()) { + if (!user.equals(userCon.getUniqueId())) { + userCon.sendData(VoiceService.CHANNEL, 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) { + UserConnection conn = voicePlayers.get(target); + if (conn != null) { + if (userDisconnectPacket == null) { + userDisconnectPacket = VoiceSignalPackets.makeVoiceSignalPacketDisconnect(user); + } + conn.sendData(VoiceService.CHANNEL, userDisconnectPacket); + } + } + } + } + } + } + +} diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/VoiceService.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/VoiceService.java new file mode 100644 index 0000000..43b7696 --- /dev/null +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/VoiceService.java @@ -0,0 +1,118 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import gnu.trove.map.TMap; +import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.api.config.ServerInfo; + +/** + * 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 VoiceService { + + public static final String CHANNEL = "EAG|Voice-1.8"; + + private final Map serverMap = new HashMap(); + private final byte[] disableVoicePacket; + + public VoiceService(EaglerBungeeConfig conf) { + this.disableVoicePacket = VoiceSignalPackets.makeVoiceSignalPacketAllowed(false, null); + String[] iceServers = conf.getICEServers().toArray(new String[conf.getICEServers().size()]); + byte[] iceServersPacket = VoiceSignalPackets.makeVoiceSignalPacketAllowed(true, iceServers); + TMap servers = BungeeCord.getInstance().config.getServers(); + Set keySet = new HashSet(servers.keySet()); + keySet.removeAll(conf.getDisableVoiceOnServersSet()); + for(String s : keySet) { + serverMap.put(s, new VoiceServerImpl(servers.get(s), iceServersPacket)); + } + } + + public void handlePlayerLoggedIn(UserConnection player) { + + } + + public void handlePlayerLoggedOut(UserConnection player) { + + } + + public void handleServerConnected(UserConnection player, ServerInfo server) { + VoiceServerImpl svr = serverMap.get(server.getName()); + if(svr != null) { + svr.handlePlayerLoggedIn(player); + }else { + player.sendData(CHANNEL, disableVoicePacket); + } + } + + public void handleServerDisconnected(UserConnection player, ServerInfo server) { + VoiceServerImpl svr = serverMap.get(server.getName()); + if(svr != null) { + svr.handlePlayerLoggedOut(player); + } + } + + void handleVoiceSignalPacketTypeRequest(UUID player, UserConnection sender) { + if(sender.getServer() != null) { + VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName()); + if(svr != null) { + svr.handleVoiceSignalPacketTypeRequest(player, sender); + } + } + } + + void handleVoiceSignalPacketTypeConnect(UserConnection sender) { + if(sender.getServer() != null) { + VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName()); + if(svr != null) { + svr.handleVoiceSignalPacketTypeConnect(sender); + } + } + } + + void handleVoiceSignalPacketTypeICE(UUID player, String str, UserConnection sender) { + if(sender.getServer() != null) { + VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName()); + if(svr != null) { + svr.handleVoiceSignalPacketTypeICE(player, str, sender); + } + } + } + + void handleVoiceSignalPacketTypeDesc(UUID player, String str, UserConnection sender) { + if(sender.getServer() != null) { + VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName()); + if(svr != null) { + svr.handleVoiceSignalPacketTypeDesc(player, str, sender); + } + } + } + + void handleVoiceSignalPacketTypeDisconnect(UUID player, UserConnection sender) { + if(sender.getServer() != null) { + VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName()); + if(svr != null) { + svr.handleVoiceSignalPacketTypeDisconnect(player, sender); + } + } + } + +} diff --git a/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/VoiceSignalPackets.java b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/VoiceSignalPackets.java new file mode 100644 index 0000000..c9b2de9 --- /dev/null +++ b/gateway/EaglercraftXBungee/src/main/java/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/voice/VoiceSignalPackets.java @@ -0,0 +1,194 @@ +package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice; + +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 net.md_5.bungee.UserConnection; +import net.md_5.bungee.protocol.DefinedPacket; + +/** + * 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, UserConnection sender, VoiceService voiceService) 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: { + voiceService.handleVoiceSignalPacketTypeRequest(DefinedPacket.readUUID(buffer), sender); + break; + } + case VOICE_SIGNAL_CONNECT: { + voiceService.handleVoiceSignalPacketTypeConnect(sender); + break; + } + case VOICE_SIGNAL_ICE: { + voiceService.handleVoiceSignalPacketTypeICE(DefinedPacket.readUUID(buffer), DefinedPacket.readString(buffer, 32767), sender); + break; + } + case VOICE_SIGNAL_DESC: { + voiceService.handleVoiceSignalPacketTypeDesc(DefinedPacket.readUUID(buffer), DefinedPacket.readString(buffer, 32767), sender); + break; + } + case VOICE_SIGNAL_DISCONNECT: { + voiceService.handleVoiceSignalPacketTypeDisconnect(buffer.readableBytes() > 0 ? DefinedPacket.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); + DefinedPacket.writeVarInt(iceServersBytes.length, wrappedBuffer); + for(int i = 0; i < iceServersBytes.length; ++i) { + byte[] b = iceServersBytes[i]; + DefinedPacket.writeVarInt(b.length, wrappedBuffer); + wrappedBuffer.writeBytes(b); + } + return ret; + } + + static byte[] makeVoiceSignalPacketGlobal(Collection users) { + int cnt = users.size(); + byte[][] displayNames = new byte[cnt][]; + int i = 0; + for(UserConnection user : users) { + String name = user.getDisplayName(); + 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); + DefinedPacket.writeVarInt(cnt, wrappedBuffer); + for(UserConnection user : users) { + DefinedPacket.writeUUID(user.getUniqueId(), wrappedBuffer); + } + for(i = 0; i < cnt; ++i) { + DefinedPacket.writeVarInt(displayNames[i].length, wrappedBuffer); + 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); + DefinedPacket.writeUUID(player, wrappedBuffer); + wrappedBuffer.writeBoolean(offer); + return ret; + } + + static byte[] makeVoiceSignalPacketConnectAnnounce(UUID player) { + byte[] ret = new byte[17]; + ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0); + wrappedBuffer.writeByte(VOICE_SIGNAL_CONNECT); + DefinedPacket.writeUUID(player, wrappedBuffer); + 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); + DefinedPacket.writeUUID(player, wrappedBuffer); + 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); + DefinedPacket.writeUUID(player, wrappedBuffer); + DefinedPacket.writeVarInt(strBytes.length, wrappedBuffer); + 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); + DefinedPacket.writeUUID(player, wrappedBuffer); + DefinedPacket.writeVarInt(strBytes.length, wrappedBuffer); + 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; + } +} diff --git a/gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/default_ice_servers.yml b/gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/default_ice_servers.yml new file mode 100644 index 0000000..5e95a73 --- /dev/null +++ b/gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/default_ice_servers.yml @@ -0,0 +1,20 @@ +voice_stun_servers: +- 'stun:stun.l.google.com:19302' +- 'stun:stun1.l.google.com:19302' +- 'stun:stun2.l.google.com:19302' +- 'stun:stun3.l.google.com:19302' +- 'stun:stun4.l.google.com:19302' +- 'stun:openrelay.metered.ca:80' +voice_turn_servers: + openrelay1: + url: 'turn:openrelay.metered.ca:80' + username: 'openrelayproject' + password: 'openrelayproject' + openrelay2: + url: 'turn:openrelay.metered.ca:443' + username: 'openrelayproject' + password: 'openrelayproject' + openrelay3: + url: 'turn:openrelay.metered.ca:443?transport=tcp' + username: 'openrelayproject' + password: 'openrelayproject' \ No newline at end of file diff --git a/gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/default_listeners.yml b/gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/default_listeners.yml index 99e185d..894f62b 100644 --- a/gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/default_listeners.yml +++ b/gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/default_listeners.yml @@ -26,6 +26,7 @@ listener_01: page_index_name: - 'index.html' - 'index.htm' + allow_voice: false ratelimit: ip: enable: true diff --git a/gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/default_settings.yml b/gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/default_settings.yml index daa5411..0be9f9c 100644 --- a/gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/default_settings.yml +++ b/gateway/EaglercraftXBungee/src/main/resources/net/lax1dude/eaglercraft/v1_8/plugin/gateway_bungeecord/config/default_settings.yml @@ -18,4 +18,8 @@ skin_cache_max_profiles: 32768 skin_cache_antagonists_ratelimit: 15 sql_driver_class: 'internal' sql_driver_path: 'internal' -eagler_players_vanilla_skin: '' \ No newline at end of file +eagler_players_vanilla_skin: '' +enable_is_eagler_player_property: true +disable_voice_chat_on_servers: [] +disable_fnaw_skins_everywhere: false +disable_fnaw_skins_on_servers: [] \ No newline at end of file diff --git a/gateway/EaglercraftXBungee/src/main/resources/plugin.yml b/gateway/EaglercraftXBungee/src/main/resources/plugin.yml index 73728ed..0e0f9b4 100644 --- a/gateway/EaglercraftXBungee/src/main/resources/plugin.yml +++ b/gateway/EaglercraftXBungee/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: EaglercraftXBungee main: net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee -version: 1.0.10 +version: 1.1.0 description: Plugin to allow EaglercraftX 1.8 players to join your network, or allow EaglercraftX 1.8 players to use your network as a proxy to join other networks author: lax1dude \ No newline at end of file diff --git a/gateway_version b/gateway_version index 437d26b..1cc5f65 100644 --- a/gateway_version +++ b/gateway_version @@ -1 +1 @@ -1.0.10 \ No newline at end of file +1.1.0 \ No newline at end of file