(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.server.web.HttpWebServer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.shit.CompatWarning; 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.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.ISkinService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService; 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.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.Plugin;
import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.netty.PipelineUtils; import net.md_5.bungee.netty.PipelineUtils;
@ -55,9 +57,8 @@ import net.md_5.bungee.BungeeCord;
*/ */
public class EaglerXBungee extends Plugin { public class EaglerXBungee extends Plugin {
public static final String NATIVE_BUNGEECORD_BUILD = "1.20-R0.3-SNAPSHOT:eda268b:1797"; public static final String NATIVE_BUNGEECORD_BUILD = "1.20-R0.3-SNAPSHOT:67c65e0:1828";
public static final String NATIVE_WATERFALL_BUILD = "1.20-R0.2-SNAPSHOT:92b5149:562"; public static final String NATIVE_WATERFALL_BUILD = "1.20-R0.3-SNAPSHOT:da6aaf6:572";
public static final String NATIVE_FLAMECORD_BUILD = "1.1.1";
static { static {
CompatWarning.displayCompatWarning(); CompatWarning.displayCompatWarning();
@ -72,6 +73,8 @@ public class EaglerXBungee extends Plugin {
private Timer authServiceTasks = null; private Timer authServiceTasks = null;
private final ChannelFutureListener newChannelListener; private final ChannelFutureListener newChannelListener;
private ISkinService skinService; private ISkinService skinService;
private CapeServiceOffline capeService;
private VoiceService voiceService;
private DefaultAuthSystem defaultAuthSystem; private DefaultAuthSystem defaultAuthSystem;
public EaglerXBungee() { public EaglerXBungee() {
@ -128,7 +131,10 @@ public class EaglerXBungee extends Plugin {
} }
} }
getProxy().registerChannel(SkinService.CHANNEL); getProxy().registerChannel(SkinService.CHANNEL);
getProxy().registerChannel(CapeServiceOffline.CHANNEL);
getProxy().registerChannel(EaglerPipeline.UPDATE_CERT_CHANNEL); getProxy().registerChannel(EaglerPipeline.UPDATE_CERT_CHANNEL);
getProxy().registerChannel(VoiceService.CHANNEL);
getProxy().registerChannel(EaglerPacketEventListener.FNAW_SKIN_ENABLE_CHANNEL);
startListeners(); startListeners();
if(skinServiceTasks != null) { if(skinServiceTasks != null) {
skinServiceTasks.cancel(); skinServiceTasks.cancel();
@ -165,6 +171,7 @@ public class EaglerXBungee extends Plugin {
} }
}, 1000l, 1000l); }, 1000l, 1000l);
} }
capeService = new CapeServiceOffline();
if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) { if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) {
try { try {
defaultAuthSystem = DefaultAuthSystem.initializeAuthSystem(authConf); defaultAuthSystem = DefaultAuthSystem.initializeAuthSystem(authConf);
@ -185,6 +192,12 @@ public class EaglerXBungee extends Plugin {
}, 60000l, 60000l); }, 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 @Override
@ -193,13 +206,19 @@ public class EaglerXBungee extends Plugin {
mgr.unregisterListeners(this); mgr.unregisterListeners(this);
mgr.unregisterCommands(this); mgr.unregisterCommands(this);
getProxy().unregisterChannel(SkinService.CHANNEL); getProxy().unregisterChannel(SkinService.CHANNEL);
getProxy().unregisterChannel(CapeServiceOffline.CHANNEL);
getProxy().unregisterChannel(EaglerPipeline.UPDATE_CERT_CHANNEL); getProxy().unregisterChannel(EaglerPipeline.UPDATE_CERT_CHANNEL);
getProxy().unregisterChannel(VoiceService.CHANNEL);
getProxy().unregisterChannel(EaglerPacketEventListener.FNAW_SKIN_ENABLE_CHANNEL);
stopListeners(); stopListeners();
if(skinServiceTasks != null) { if(skinServiceTasks != null) {
skinServiceTasks.cancel(); skinServiceTasks.cancel();
skinServiceTasks = null; skinServiceTasks = null;
} }
skinService.shutdown(); skinService.shutdown();
skinService = null;
capeService.shutdown();
capeService = null;
if(defaultAuthSystem != null) { if(defaultAuthSystem != null) {
defaultAuthSystem.destroy(); defaultAuthSystem.destroy();
defaultAuthSystem = null; defaultAuthSystem = null;
@ -208,6 +227,7 @@ public class EaglerXBungee extends Plugin {
authServiceTasks = null; authServiceTasks = null;
} }
} }
voiceService = null;
BinaryHttpClient.killEventLoop(); BinaryHttpClient.killEventLoop();
} }
@ -278,10 +298,18 @@ public class EaglerXBungee extends Plugin {
return skinService; return skinService;
} }
public CapeServiceOffline getCapeService() {
return capeService;
}
public DefaultAuthSystem getAuthService() { public DefaultAuthSystem getAuthService() {
return defaultAuthSystem; return defaultAuthSystem;
} }
public VoiceService getVoiceService() {
return voiceService;
}
public static EaglerXBungee getEagler() { public static EaglerXBungee getEagler() {
return instance; return instance;
} }

View File

@ -9,12 +9,14 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
@ -31,7 +33,7 @@ import net.md_5.bungee.config.YamlConfiguration;
import net.md_5.bungee.protocol.Property; 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 * 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 * 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")); Configuration listenerYml = prov.load(getConfigFile(directory, "listeners.yml"));
Iterator<String> listeners = listenerYml.getKeys().iterator(); Iterator<String> listeners = listenerYml.getKeys().iterator();
Map<String, EaglerListenerConfig> serverListeners = new HashMap(); Map<String, EaglerListenerConfig> serverListeners = new HashMap();
boolean voiceChat = false;
while(listeners.hasNext()) { while(listeners.hasNext()) {
String name = listeners.next(); String name = listeners.next();
EaglerListenerConfig conf = EaglerListenerConfig.loadConfig(listenerYml.getSection(name), contentTypes); EaglerListenerConfig conf = EaglerListenerConfig.loadConfig(listenerYml.getSection(name), contentTypes);
if(conf != null) { if(conf != null) {
serverListeners.put(name, conf); serverListeners.put(name, conf);
voiceChat |= conf.getEnableVoiceChat();
}else { }else {
EaglerXBungee.logger().severe("Invalid listener config: " + name); EaglerXBungee.logger().severe("Invalid listener config: " + name);
} }
@ -105,6 +109,9 @@ public class EaglerBungeeConfig {
Configuration updatesYml = prov.load(getConfigFile(directory, "updates.yml")); Configuration updatesYml = prov.load(getConfigFile(directory, "updates.yml"));
EaglerUpdateConfig updatesConfig = EaglerUpdateConfig.loadConfig(updatesYml); EaglerUpdateConfig updatesConfig = EaglerUpdateConfig.loadConfig(updatesYml);
Configuration iceServersYml = prov.load(getConfigFile(directory, "ice_servers.yml"));
Collection<String> iceServers = loadICEServers(iceServersYml);
if(authConfig.isEnableAuthentication()) { if(authConfig.isEnableAuthentication()) {
for(EaglerListenerConfig lst : serverListeners.values()) { for(EaglerListenerConfig lst : serverListeners.values()) {
if(lst.getRatelimitLogin() != null) lst.getRatelimitLogin().setDivisor(2); if(lst.getRatelimitLogin() != null) lst.getRatelimitLogin().setDivisor(2);
@ -135,12 +142,18 @@ public class EaglerBungeeConfig {
if(eaglerPlayersVanillaSkin != null && eaglerPlayersVanillaSkin.length() == 0) { if(eaglerPlayersVanillaSkin != null && eaglerPlayersVanillaSkin.length() == 0) {
eaglerPlayersVanillaSkin = null; 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, final EaglerBungeeConfig ret = new EaglerBungeeConfig(serverName, serverUUID, websocketKeepAliveTimeout,
websocketHandshakeTimeout, websocketCompressionLevel, serverListeners, contentTypes, websocketHandshakeTimeout, websocketCompressionLevel, serverListeners, contentTypes,
downloadVanillaSkins, validSkinUrls, uuidRateLimitPlayer, uuidRateLimitGlobal, skinRateLimitPlayer, downloadVanillaSkins, validSkinUrls, uuidRateLimitPlayer, uuidRateLimitGlobal, skinRateLimitPlayer,
skinRateLimitGlobal, skinCacheURI, keepObjectsDays, keepProfilesDays, maxObjects, maxProfiles, 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) { if(eaglerPlayersVanillaSkin != null) {
VanillaDefaultSkinProfileLoader.lookupVanillaSkinUser(ret); 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") @SuppressWarnings("deprecation")
private static JsonObject parseJsonObject(InputStream file) throws IOException { private static JsonObject parseJsonObject(InputStream file) throws IOException {
StringBuilder str = new StringBuilder(); StringBuilder str = new StringBuilder();
@ -243,8 +268,14 @@ public class EaglerBungeeConfig {
private final String sqliteDriverClass; private final String sqliteDriverClass;
private final String sqliteDriverPath; private final String sqliteDriverPath;
private final String eaglerPlayersVanillaSkin; private final String eaglerPlayersVanillaSkin;
private final boolean enableIsEaglerPlayerProperty;
private final EaglerAuthConfig authConfig; private final EaglerAuthConfig authConfig;
private final EaglerUpdateConfig updateConfig; 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 }; Property[] eaglerPlayersVanillaSkinCached = new Property[] { isEaglerProperty };
public String getServerName() { public String getServerName() {
@ -362,6 +393,10 @@ public class EaglerBungeeConfig {
return eaglerPlayersVanillaSkin; return eaglerPlayersVanillaSkin;
} }
public boolean getEnableIsEaglerPlayerProperty() {
return enableIsEaglerPlayerProperty;
}
public Property[] getEaglerPlayersVanillaSkinProperties() { public Property[] getEaglerPlayersVanillaSkinProperties() {
return eaglerPlayersVanillaSkinCached; return eaglerPlayersVanillaSkinCached;
} }
@ -378,6 +413,26 @@ public class EaglerBungeeConfig {
return updateConfig; 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, private EaglerBungeeConfig(String serverName, UUID serverUUID, long websocketKeepAliveTimeout,
long websocketHandshakeTimeout, int httpWebsocketCompressionLevel, long websocketHandshakeTimeout, int httpWebsocketCompressionLevel,
Map<String, EaglerListenerConfig> serverListeners, Map<String, HttpContentType> contentTypes, Map<String, EaglerListenerConfig> serverListeners, Map<String, HttpContentType> contentTypes,
@ -385,7 +440,9 @@ public class EaglerBungeeConfig {
int uuidRateLimitGlobal, int skinRateLimitPlayer, int skinRateLimitGlobal, String skinCacheURI, int uuidRateLimitGlobal, int skinRateLimitPlayer, int skinRateLimitGlobal, String skinCacheURI,
int keepObjectsDays, int keepProfilesDays, int maxObjects, int maxProfiles, int antagonistsRateLimit, int keepObjectsDays, int keepProfilesDays, int maxObjects, int maxProfiles, int antagonistsRateLimit,
String sqliteDriverClass, String sqliteDriverPath, String eaglerPlayersVanillaSkin, 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.serverName = serverName;
this.serverUUID = serverUUID; this.serverUUID = serverUUID;
this.serverListeners = serverListeners; this.serverListeners = serverListeners;
@ -408,8 +465,14 @@ public class EaglerBungeeConfig {
this.sqliteDriverClass = sqliteDriverClass; this.sqliteDriverClass = sqliteDriverClass;
this.sqliteDriverPath = sqliteDriverPath; this.sqliteDriverPath = sqliteDriverPath;
this.eaglerPlayersVanillaSkin = eaglerPlayersVanillaSkin; this.eaglerPlayersVanillaSkin = eaglerPlayersVanillaSkin;
this.enableIsEaglerPlayerProperty = enableIsEaglerPlayerProperty;
this.authConfig = authConfig; this.authConfig = authConfig;
this.updateConfig = updateConfig; 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); contentTypes, indexPage, page404);
} }
boolean enableVoiceChat = config.getBoolean("allow_voice", false);
EaglerRateLimiter ratelimitIp = null; EaglerRateLimiter ratelimitIp = null;
EaglerRateLimiter ratelimitLogin = null; EaglerRateLimiter ratelimitLogin = null;
EaglerRateLimiter ratelimitMOTD = null; EaglerRateLimiter ratelimitMOTD = null;
@ -149,7 +151,7 @@ public class EaglerListenerConfig extends ListenerInfo {
cacheTrending, cachePortfolios); cacheTrending, cachePortfolios);
return new EaglerListenerConfig(hostv4, hostv6, maxPlayer, tabListType, defaultServer, forceDefaultServer, return new EaglerListenerConfig(hostv4, hostv6, maxPlayer, tabListType, defaultServer, forceDefaultServer,
forwardIp, forwardIpHeader, redirectLegacyClientsTo, serverIcon, serverMOTD, allowMOTD, allowQuery, forwardIp, forwardIpHeader, redirectLegacyClientsTo, serverIcon, serverMOTD, allowMOTD, allowQuery,
cacheConfig, httpServer, ratelimitIp, ratelimitLogin, ratelimitMOTD, ratelimitQuery); cacheConfig, httpServer, enableVoiceChat, ratelimitIp, ratelimitLogin, ratelimitMOTD, ratelimitQuery);
} }
private final InetSocketAddress address; private final InetSocketAddress address;
@ -169,6 +171,7 @@ public class EaglerListenerConfig extends ListenerInfo {
private final HttpWebServer webServer; private final HttpWebServer webServer;
private boolean serverIconSet = false; private boolean serverIconSet = false;
private int[] serverIconPixels = null; private int[] serverIconPixels = null;
private final boolean enableVoiceChat;
private final EaglerRateLimiter ratelimitIp; private final EaglerRateLimiter ratelimitIp;
private final EaglerRateLimiter ratelimitLogin; private final EaglerRateLimiter ratelimitLogin;
private final EaglerRateLimiter ratelimitMOTD; private final EaglerRateLimiter ratelimitMOTD;
@ -178,8 +181,8 @@ public class EaglerListenerConfig extends ListenerInfo {
String tabListType, String defaultServer, boolean forceDefaultServer, boolean forwardIp, String tabListType, String defaultServer, boolean forceDefaultServer, boolean forwardIp,
String forwardIpHeader, String redirectLegacyClientsTo, String serverIcon, List<String> serverMOTD, String forwardIpHeader, String redirectLegacyClientsTo, String serverIcon, List<String> serverMOTD,
boolean allowMOTD, boolean allowQuery, MOTDCacheConfiguration motdCacheConfig, HttpWebServer webServer, boolean allowMOTD, boolean allowQuery, MOTDCacheConfiguration motdCacheConfig, HttpWebServer webServer,
EaglerRateLimiter ratelimitIp, EaglerRateLimiter ratelimitLogin, EaglerRateLimiter ratelimitMOTD, boolean enableVoiceChat, EaglerRateLimiter ratelimitIp, EaglerRateLimiter ratelimitLogin,
EaglerRateLimiter ratelimitQuery) { EaglerRateLimiter ratelimitMOTD, EaglerRateLimiter ratelimitQuery) {
super(address, String.join("\n", serverMOTD), maxPlayer, 60, Arrays.asList(defaultServer), forceDefaultServer, super(address, String.join("\n", serverMOTD), maxPlayer, 60, Arrays.asList(defaultServer), forceDefaultServer,
Collections.emptyMap(), tabListType, false, false, 0, false, false); Collections.emptyMap(), tabListType, false, false, 0, false, false);
this.address = address; this.address = address;
@ -197,6 +200,7 @@ public class EaglerListenerConfig extends ListenerInfo {
this.allowQuery = allowQuery; this.allowQuery = allowQuery;
this.motdCacheConfig = motdCacheConfig; this.motdCacheConfig = motdCacheConfig;
this.webServer = webServer; this.webServer = webServer;
this.enableVoiceChat = enableVoiceChat;
this.ratelimitIp = ratelimitIp; this.ratelimitIp = ratelimitIp;
this.ratelimitLogin = ratelimitLogin; this.ratelimitLogin = ratelimitLogin;
this.ratelimitMOTD = ratelimitMOTD; this.ratelimitMOTD = ratelimitMOTD;
@ -285,6 +289,10 @@ public class EaglerListenerConfig extends ListenerInfo {
return redirectLegacyClientsTo; return redirectLegacyClientsTo;
} }
public boolean getEnableVoiceChat() {
return enableVoiceChat;
}
public EaglerRateLimiter getRatelimitIp() { public EaglerRateLimiter getRatelimitIp() {
return ratelimitIp; return ratelimitIp;
} }

View File

@ -2,6 +2,7 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
import org.apache.commons.codec.binary.Base64; 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.auth.DefaultAuthSystem;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerAuthConfig; 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.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.SkinPackets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService; 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.UserConnection;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent; 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.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PluginMessageEvent; import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.PostLoginEvent; 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.api.plugin.Listener;
import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult; import net.md_5.bungee.connection.LoginResult;
@ -47,6 +55,8 @@ import net.md_5.bungee.protocol.Property;
*/ */
public class EaglerPacketEventListener implements Listener { public class EaglerPacketEventListener implements Listener {
public static final String FNAW_SKIN_ENABLE_CHANNEL = "EAG|FNAWSEn-1.8";
public final EaglerXBungee plugin; public final EaglerXBungee plugin;
public EaglerPacketEventListener(EaglerXBungee plugin) { public EaglerPacketEventListener(EaglerXBungee plugin) {
@ -56,28 +66,46 @@ public class EaglerPacketEventListener implements Listener {
@EventHandler @EventHandler
public void onPluginMessage(final PluginMessageEvent event) { public void onPluginMessage(final PluginMessageEvent event) {
if(event.getSender() instanceof UserConnection) { if(event.getSender() instanceof UserConnection) {
if(SkinService.CHANNEL.equals(event.getTag())) { final UserConnection player = (UserConnection)event.getSender();
event.setCancelled(true); if(player.getPendingConnection() instanceof EaglerInitialHandler) {
final UserConnection sender = (UserConnection)event.getSender(); if(SkinService.CHANNEL.equals(event.getTag())) {
if(sender.getPendingConnection() instanceof EaglerInitialHandler) { event.setCancelled(true);
ProxyServer.getInstance().getScheduler().runAsync(plugin, new Runnable() { ProxyServer.getInstance().getScheduler().runAsync(plugin, new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
SkinPackets.processPacket(event.getData(), sender, plugin.getSkinService()); SkinPackets.processPacket(event.getData(), player, plugin.getSkinService());
} catch (IOException e) { } catch (IOException e) {
event.getSender().disconnect(new TextComponent("Skin packet error!")); 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 { }else if(CapeServiceOffline.CHANNEL.equals(event.getTag())) {
event.getSender().disconnect(new TextComponent("Cannot send \"" + SkinService.CHANNEL + "\" on a non-eagler connection!")); 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) { }else if(event.getSender() instanceof Server && event.getReceiver() instanceof UserConnection) {
UserConnection player = (UserConnection)event.getReceiver(); UserConnection player = (UserConnection)event.getReceiver();
if("EAG|GetDomain".equals(event.getTag()) && player.getPendingConnection() instanceof EaglerInitialHandler) { if("EAG|GetDomain".equals(event.getTag()) && player.getPendingConnection() instanceof EaglerInitialHandler) {
event.setCancelled(true);
String domain = ((EaglerInitialHandler)player.getPendingConnection()).getOrigin(); String domain = ((EaglerInitialHandler)player.getPendingConnection()).getOrigin();
if(domain == null) { if(domain == null) {
((Server)event.getSender()).sendData("EAG|Domain", new byte[] { 0 }); ((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()) { if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) {
DefaultAuthSystem srv = EaglerXBungee.getEagler().getAuthService(); DefaultAuthSystem srv = plugin.getAuthService();
if(srv != null) { if(srv != null) {
srv.handleVanillaLogin(event); srv.handleVanillaLogin(event);
} }
@ -131,6 +159,47 @@ public class EaglerPacketEventListener implements Listener {
@EventHandler @EventHandler
public void onConnectionLost(PlayerDisconnectEvent event) { 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.TIntSet;
import gnu.trove.set.hash.TIntHashSet; 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.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.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SimpleRateLimiter;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.chat.BaseComponent; 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.InitialHandler;
import net.md_5.bungee.connection.LoginResult; import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
@ -67,12 +68,17 @@ public class EaglerInitialHandler extends InitialHandler {
public final SimpleRateLimiter skinLookupRateLimiter; public final SimpleRateLimiter skinLookupRateLimiter;
public final SimpleRateLimiter skinUUIDLookupRateLimiter; public final SimpleRateLimiter skinUUIDLookupRateLimiter;
public final SimpleRateLimiter skinTextureDownloadRateLimiter; public final SimpleRateLimiter skinTextureDownloadRateLimiter;
public final SimpleRateLimiter capeLookupRateLimiter;
public final SimpleRateLimiter voiceConnectRateLimiter;
public final String origin; public final String origin;
public final ClientCertificateHolder clientCertificate; public final ClientCertificateHolder clientCertificate;
public final Set<ClientCertificateHolder> certificatesToSend; public final Set<ClientCertificateHolder> certificatesToSend;
public final TIntSet certificatesSent; 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, int gameProtocolVersion, String username, UUID playerUUID, InetSocketAddress address, String host,
String origin, ClientCertificateHolder clientCertificate) { String origin, ClientCertificateHolder clientCertificate) {
super(bungee, listener); super(bungee, listener);
@ -84,6 +90,8 @@ public class EaglerInitialHandler extends InitialHandler {
this.skinLookupRateLimiter = new SimpleRateLimiter(); this.skinLookupRateLimiter = new SimpleRateLimiter();
this.skinUUIDLookupRateLimiter = new SimpleRateLimiter(); this.skinUUIDLookupRateLimiter = new SimpleRateLimiter();
this.skinTextureDownloadRateLimiter = new SimpleRateLimiter(); this.skinTextureDownloadRateLimiter = new SimpleRateLimiter();
this.capeLookupRateLimiter = new SimpleRateLimiter();
this.voiceConnectRateLimiter = new SimpleRateLimiter();
this.clientCertificate = clientCertificate; this.clientCertificate = clientCertificate;
this.certificatesToSend = new HashSet(); this.certificatesToSend = new HashSet();
this.certificatesSent = new TIntHashSet(); this.certificatesSent = new TIntHashSet();
@ -108,7 +116,11 @@ public class EaglerInitialHandler extends InitialHandler {
ch.getHandle().writeAndFlush(arg0); 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 { try {
super.connected(ch); super.connected(ch);
} catch (Exception e) { } catch (Exception e) {
@ -251,4 +263,7 @@ public class EaglerInitialHandler extends InitialHandler {
return origin; 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.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.MOTDQueryHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.QueryManager; 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.SkinPackets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService; import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService;
import net.md_5.bungee.BungeeCord; 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; ServerInfo server;
if (bungee.getReconnectHandler() != null) { if (bungee.getReconnectHandler() != null) {
@ -962,8 +977,25 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
server = bungee.getServerInfo(conf.getDefaultServer()); server = bungee.getServerInfo(conf.getDefaultServer());
} }
eaglerCon.hasBeenForwarded = true; final ServerInfo server2 = server;
userCon.connect(server, null, true, ServerConnectEvent.Reason.JOIN_PROXY); 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, ":> - BungeeCord: " + EaglerXBungee.NATIVE_BUNGEECORD_BUILD,
":> - Waterfall: " + EaglerXBungee.NATIVE_WATERFALL_BUILD, ":> - Waterfall: " + EaglerXBungee.NATIVE_WATERFALL_BUILD,
":> - FlameCord: " + EaglerXBungee.NATIVE_FLAMECORD_BUILD,
":> ", ":> ",
":> This is not a Bukkit/Spigot plugin!", ":> This is not a Bukkit/Spigot plugin!",
":> ", ":> ",

View File

@ -108,7 +108,7 @@ public class AsyncSkinProvider {
byte[] loadedPixels = new byte[16384]; byte[] loadedPixels = new byte[16384];
image.getRGB(0, 0, 64, 64, tmp, 0, 64); image.getRGB(0, 0, 64, 64, tmp, 0, 64);
SkinRescaler.convertToBytes(tmp, loadedPixels); SkinRescaler.convertToBytes(tmp, loadedPixels);
SkinPackets.setAlphaForChest(loadedPixels, (byte)255); SkinPackets.setAlphaForChest(loadedPixels, (byte)255, 0);
doAccept(loadedPixels); doAccept(loadedPixels);
return; return;
}else if(srcWidth == 64 && srcHeight == 32) { }else if(srcWidth == 64 && srcHeight == 32) {
@ -116,7 +116,7 @@ public class AsyncSkinProvider {
byte[] loadedPixels = new byte[16384]; byte[] loadedPixels = new byte[16384];
image.getRGB(0, 0, 64, 32, tmp1, 0, 64); image.getRGB(0, 0, 64, 32, tmp1, 0, 64);
SkinRescaler.convert64x32To64x64(tmp1, loadedPixels); SkinRescaler.convert64x32To64x64(tmp1, loadedPixels);
SkinPackets.setAlphaForChest(loadedPixels, (byte)255); SkinPackets.setAlphaForChest(loadedPixels, (byte)255, 0);
doAccept(loadedPixels); doAccept(loadedPixels);
return; return;
}else { }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; 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 * 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 * 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)); generatedPacket = SkinPackets.makePresetResponse(clientUUID, (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF));
break; break;
case PACKET_MY_SKIN_CUSTOM: case PACKET_MY_SKIN_CUSTOM:
byte[] pixels = new byte[16384]; if(bs.length != 2 + 16384) {
if(bs.length != 2 + pixels.length) {
throw new IOException("Invalid length " + bs.length + " for custom skin packet"); throw new IOException("Invalid length " + bs.length + " for custom skin packet");
} }
setAlphaForChest(pixels, (byte)255); setAlphaForChest(bs, (byte)255, 2);
System.arraycopy(bs, 2, pixels, 0, pixels.length); generatedPacket = SkinPackets.makeCustomResponse(clientUUID, (skinModel = (int)bs[1] & 0xFF), bs, 2, 16384);
generatedPacket = SkinPackets.makeCustomResponse(clientUUID, (skinModel = (int)bs[1] & 0xFF), pixels);
break; break;
default: default:
throw new IOException("Unknown skin packet type: " + packetType); throw new IOException("Unknown skin packet type: " + packetType);
@ -130,13 +128,13 @@ public class SkinPackets {
skinService.registerEaglercraftPlayer(clientUUID, generatedPacket, skinModel); skinService.registerEaglercraftPlayer(clientUUID, generatedPacket, skinModel);
} }
public static void setAlphaForChest(byte[] skin64x64, byte alpha) { public static void setAlphaForChest(byte[] skin64x64, byte alpha, int offset) {
if(skin64x64.length != 16384) { if(skin64x64.length - offset != 16384) {
throw new IllegalArgumentException("Skin is not 64x64!"); throw new IllegalArgumentException("Skin is not 64x64!");
} }
for(int y = 20; y < 32; ++y) { for(int y = 20; y < 32; ++y) {
for(int x = 16; x < 40; ++x) { 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) { 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; ret[0] = (byte)PACKET_OTHER_SKIN_CUSTOM;
UUIDToBytes(uuid, ret, 1); UUIDToBytes(uuid, ret, 1);
ret[17] = (byte)model; ret[17] = (byte)model;
System.arraycopy(pixels, 0, ret, 18, pixels.length); System.arraycopy(pixels, offset, ret, 18, length);
return ret; return ret;
} }

View File

@ -52,12 +52,6 @@ public class SkinService implements ISkinService {
public static final int masterRateLimitPerPlayer = 250; 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"; public static final String CHANNEL = "EAG|Skins-1.8";
private final Map<UUID, CachedPlayerSkin> onlinePlayersCache = new HashMap(); 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: page_index_name:
- 'index.html' - 'index.html'
- 'index.htm' - 'index.htm'
allow_voice: false
ratelimit: ratelimit:
ip: ip:
enable: true enable: true

View File

@ -19,3 +19,7 @@ skin_cache_antagonists_ratelimit: 15
sql_driver_class: 'internal' sql_driver_class: 'internal'
sql_driver_path: '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 name: EaglercraftXBungee
main: net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee 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 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 author: lax1dude

View File

@ -1 +1 @@
1.0.10 1.1.0