(1.1.0) Added capes, voice, FNAW skin disabler to plugin

This commit is contained in:
lax1dude 2024-04-03 22:18:27 -07:00
parent 0821e23613
commit 42c57894f9
22 changed files with 1098 additions and 50 deletions

View File

@ -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;
}

View File

@ -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<String> listeners = listenerYml.getKeys().iterator();
Map<String, EaglerListenerConfig> 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<String> 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<String> disableVoiceOnServers = new HashSet((Collection<String>)configYml.getList("disable_voice_chat_on_servers"));
boolean disableFNAWSkinsEverywhere = configYml.getBoolean("disable_fnaw_skins_everywhere", false);
Set<String> disableFNAWSkinsOnServers = new HashSet((Collection<String>)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<String> loadICEServers(Configuration config) {
Collection<String> ret = new ArrayList(config.getList("voice_stun_servers"));
Configuration turnServers = config.getSection("voice_turn_servers");
Iterator<String> 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<String> iceServers;
private final boolean enableVoiceChat;
private final Set<String> disableVoiceOnServers;
private final boolean disableFNAWSkinsEverywhere;
private final Set<String> 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<String> getICEServers() {
return iceServers;
}
public boolean getEnableVoiceChat() {
return enableVoiceChat;
}
public Set<String> getDisableVoiceOnServersSet() {
return disableVoiceOnServers;
}
public boolean getDisableFNAWSkinsEverywhere() {
return disableFNAWSkinsEverywhere;
}
public Set<String> getDisableFNAWSkinsOnServersSet() {
return disableFNAWSkinsOnServers;
}
private EaglerBungeeConfig(String serverName, UUID serverUUID, long websocketKeepAliveTimeout,
long websocketHandshakeTimeout, int httpWebsocketCompressionLevel,
Map<String, EaglerListenerConfig> serverListeners, Map<String, HttpContentType> 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<String> iceServers, boolean enableVoiceChat, Set<String> disableVoiceOnServers,
boolean disableFNAWSkinsEverywhere, Set<String> 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;
}
}

View File

@ -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<String> 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;
}

View File

@ -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());
}
}
}
}

View File

@ -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<ClientCertificateHolder> 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();
}
}

View File

@ -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<PostLoginEvent> complete = new Callback<PostLoginEvent>() {
@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));
}
}
});

View File

@ -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!",
":> ",

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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<UUID, byte[]> 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();
}
}
}

View File

@ -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;
}

View File

@ -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<UUID, CachedPlayerSkin> onlinePlayersCache = new HashMap();

View File

@ -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<T> extends HashSet<T> {
private final long expiration;
private final ExpiringEvent<T> event;
private final Map<T, Long> timestamps = new HashMap<>();
public ExpiringSet(long expiration) {
this.expiration = expiration;
this.event = null;
}
public ExpiringSet(long expiration, ExpiringEvent<T> event) {
this.expiration = expiration;
this.event = event;
}
public interface ExpiringEvent<T> {
void onExpiration(T item);
}
public void checkForExpirations() {
Iterator<T> 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);
}
}

View File

@ -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<UUID, UserConnection> voicePlayers = new HashMap<>();
private final Map<UUID, ExpiringSet<UUID>> voiceRequests = new HashMap<>();
private final Set<VoicePair> 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<UUID> 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<UUID> 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<VoicePair> 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<VoicePair> 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);
}
}
}
}
}
}
}

View File

@ -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<String, VoiceServerImpl> 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<String,ServerInfo> servers = BungeeCord.getInstance().config.getServers();
Set<String> 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);
}
}
}
}

View File

@ -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<UserConnection> 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;
}
}

View File

@ -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'

View File

@ -26,6 +26,7 @@ listener_01:
page_index_name:
- 'index.html'
- 'index.htm'
allow_voice: false
ratelimit:
ip:
enable: true

View File

@ -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: ''
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: []

View File

@ -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

View File

@ -1 +1 @@
1.0.10
1.1.0