(1.3.0) Add protocol V4 support to EaglerXBungee

This commit is contained in:
lax1dude
2024-09-21 12:02:00 -07:00
parent afa7e69cfa
commit 1cf3a85c2a
91 changed files with 12133 additions and 1425 deletions

View File

@ -4,6 +4,7 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
@ -15,7 +16,10 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.EaglerBackendRPCProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.DefaultAuthSystem;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandClientBrand;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandConfirmCode;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandDomain;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandEaglerPurge;
@ -27,6 +31,7 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerList
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers.EaglerPacketEventListener;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers.EaglerPluginEventListener;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerUpdateSvc;
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;
@ -35,7 +40,9 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.ISkinServic
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.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginDescription;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.BungeeCord;
@ -57,7 +64,7 @@ import net.md_5.bungee.BungeeCord;
*/
public class EaglerXBungee extends Plugin {
public static final String NATIVE_BUNGEECORD_BUILD = "1.21-R0.1-SNAPSHOT:cda4537:1851";
public static final String NATIVE_BUNGEECORD_BUILD = "1.21-R0.1-SNAPSHOT:acb85e3:1871";
public static final String NATIVE_WATERFALL_BUILD = "1.21-R0.1-SNAPSHOT:de8345a:579";
static {
@ -72,6 +79,7 @@ public class EaglerXBungee extends Plugin {
private Timer closeInactiveConnections = null;
private Timer skinServiceTasks = null;
private Timer authServiceTasks = null;
private Timer updateServiceTasks = null;
private final ChannelFutureListener newChannelListener;
private ISkinService skinService;
private CapeServiceOffline capeService;
@ -80,7 +88,7 @@ public class EaglerXBungee extends Plugin {
public EaglerXBungee() {
instance = this;
openChannels = new LinkedList();
openChannels = new LinkedList<>();
newChannelListener = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture ch) throws Exception {
@ -99,6 +107,12 @@ public class EaglerXBungee extends Plugin {
@Override
public void onLoad() {
Map<String, String> templateGlobals = EaglerXBungeeAPIHelper.getTemplateGlobals();
PluginDescription desc = this.getDescription();
templateGlobals.put("plugin_name", desc.getName());
templateGlobals.put("plugin_version", desc.getVersion());
templateGlobals.put("plugin_authors", desc.getAuthor());
templateGlobals.put("plugin_description", desc.getDescription());
try {
eventLoopGroup = ((BungeeCord) getProxy()).eventLoops;
} catch (NoSuchFieldError e) {
@ -120,6 +134,7 @@ public class EaglerXBungee extends Plugin {
mgr.registerCommand(this, new CommandRatelimit());
mgr.registerCommand(this, new CommandConfirmCode());
mgr.registerCommand(this, new CommandDomain());
mgr.registerCommand(this, new CommandClientBrand());
EaglerAuthConfig authConf = conf.getAuthConfig();
conf.setCracked(!BungeeCord.getInstance().getConfig().isOnlineMode() || !authConf.isEnableAuthentication());
if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) {
@ -131,11 +146,11 @@ public class EaglerXBungee extends Plugin {
mgr.registerCommand(this, new CommandEaglerPurge(authConf.getEaglerCommandName()));
}
}
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);
for(String str : GamePluginMessageProtocol.getAllChannels()) {
getProxy().registerChannel(str);
}
getProxy().registerChannel(EaglerBackendRPCProtocol.CHANNEL_NAME);
getProxy().registerChannel(EaglerBackendRPCProtocol.CHANNEL_NAME_READY);
getProxy().registerChannel(EaglerPacketEventListener.GET_DOMAIN_CHANNEL);
startListeners();
if(closeInactiveConnections != null) {
@ -206,6 +221,23 @@ public class EaglerXBungee extends Plugin {
}else {
logger().info("Voice chat disabled, add \"allow_voice: true\" to your listeners to enable");
}
if(updateServiceTasks != null) {
updateServiceTasks.cancel();
updateServiceTasks = null;
}
if(!conf.getUpdateConfig().isBlockAllClientUpdates()) {
updateServiceTasks = new Timer("EaglerXBungee: Update Service Tasks");
updateServiceTasks.schedule(new TimerTask() {
@Override
public void run() {
try {
EaglerUpdateSvc.updateTick();
}catch(Throwable t) {
logger().log(Level.SEVERE, "Error ticking update service!", t);
}
}
}, 0l, 5000l);
}
}
@Override
@ -213,11 +245,12 @@ public class EaglerXBungee extends Plugin {
PluginManager mgr = getProxy().getPluginManager();
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);
for(String str : GamePluginMessageProtocol.getAllChannels()) {
getProxy().unregisterChannel(str);
}
getProxy().unregisterChannel(EaglerBackendRPCProtocol.CHANNEL_NAME);
getProxy().unregisterChannel(EaglerBackendRPCProtocol.CHANNEL_NAME_READY);
getProxy().unregisterChannel(EaglerPacketEventListener.GET_DOMAIN_CHANNEL);
stopListeners();
if(closeInactiveConnections != null) {
closeInactiveConnections.cancel();
@ -227,6 +260,10 @@ public class EaglerXBungee extends Plugin {
skinServiceTasks.cancel();
skinServiceTasks = null;
}
if(updateServiceTasks != null) {
updateServiceTasks.cancel();
updateServiceTasks = null;
}
skinService.shutdown();
skinService = null;
capeService.shutdown();

View File

@ -0,0 +1,22 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api;
/**
* 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 enum EnumVoiceState {
SERVER_DISABLE,
DISABLED,
ENABLED;
}

View File

@ -0,0 +1,23 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api;
/**
* 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 enum EnumWebViewState {
NOT_SUPPORTED,
SERVER_DISABLE,
CHANNEL_CLOSED,
CHANNEL_OPEN;
}

View File

@ -0,0 +1,362 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeShowV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeShowV4EAG.EnumBadgePriority;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
/**
* 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 NotificationBadgeBuilder {
public static enum BadgePriority {
LOW, NORMAL, HIGHER, HIGHEST;
}
private UUID badgeUUID = null;
private BaseComponent bodyComponent = null;
private BaseComponent titleComponent = null;
private BaseComponent sourceComponent = null;
private long originalTimestampSec = 0l;
private boolean silent = false;
private BadgePriority priority = BadgePriority.NORMAL;
private UUID mainIconUUID = null;
private UUID titleIconUUID = null;
private int hideAfterSec = 10;
private int expireAfterSec = 3600;
private int backgroundColor = 0xFFFFFF;
private int bodyTxtColor = 0xFFFFFF;
private int titleTxtColor = 0xFFFFFF;
private int sourceTxtColor = 0xFFFFFF;
private SPacketNotifBadgeShowV4EAG packetCache = null;
private boolean packetDirty = true;
public NotificationBadgeBuilder() {
originalTimestampSec = System.currentTimeMillis() / 1000l;
}
public NotificationBadgeBuilder(NotificationBadgeBuilder builder) {
badgeUUID = builder.badgeUUID;
bodyComponent = builder.bodyComponent;
titleComponent = builder.titleComponent;
sourceComponent = builder.sourceComponent;
originalTimestampSec = builder.originalTimestampSec;
silent = builder.silent;
priority = builder.priority;
mainIconUUID = builder.mainIconUUID;
titleIconUUID = builder.titleIconUUID;
hideAfterSec = builder.hideAfterSec;
backgroundColor = builder.backgroundColor;
bodyTxtColor = builder.bodyTxtColor;
titleTxtColor = builder.titleTxtColor;
sourceTxtColor = builder.sourceTxtColor;
packetCache = !builder.packetDirty ? builder.packetCache : null;
packetDirty = builder.packetDirty;
}
public NotificationBadgeBuilder(SPacketNotifBadgeShowV4EAG packet) {
badgeUUID = new UUID(packet.badgeUUIDMost, packet.badgeUUIDLeast);
try {
bodyComponent = ComponentSerializer.deserialize(packet.bodyComponent);
}catch(Throwable t) {
bodyComponent = new TextComponent(packet.bodyComponent);
}
try {
titleComponent = ComponentSerializer.deserialize(packet.titleComponent);
}catch(Throwable t) {
titleComponent = new TextComponent(packet.titleComponent);
}
try {
sourceComponent = ComponentSerializer.deserialize(packet.sourceComponent);
}catch(Throwable t) {
sourceComponent = new TextComponent(packet.sourceComponent);
}
originalTimestampSec = packet.originalTimestampSec;
silent = packet.silent;
switch(packet.priority) {
case LOW:
default:
priority = BadgePriority.LOW;
break;
case NORMAL:
priority = BadgePriority.NORMAL;
break;
case HIGHER:
priority = BadgePriority.HIGHER;
break;
case HIGHEST:
priority = BadgePriority.HIGHEST;
break;
}
mainIconUUID = new UUID(packet.mainIconUUIDMost, packet.mainIconUUIDLeast);
titleIconUUID = new UUID(packet.titleIconUUIDMost, packet.titleIconUUIDLeast);
hideAfterSec = packet.hideAfterSec;
backgroundColor = packet.backgroundColor;
bodyTxtColor = packet.bodyTxtColor;
titleTxtColor = packet.titleTxtColor;
sourceTxtColor = packet.sourceTxtColor;
packetCache = packet;
packetDirty = false;
}
public UUID getBadgeUUID() {
return badgeUUID;
}
public NotificationBadgeBuilder setBadgeUUID(UUID badgeUUID) {
this.badgeUUID = badgeUUID;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setBadgeUUIDRandom() {
this.badgeUUID = UUID.randomUUID();
this.packetDirty = true;
return this;
}
public BaseComponent getBodyComponent() {
return bodyComponent;
}
public NotificationBadgeBuilder setBodyComponent(BaseComponent bodyComponent) {
this.bodyComponent = bodyComponent;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setBodyComponent(String bodyText) {
this.bodyComponent = new TextComponent(bodyText);
this.packetDirty = true;
return this;
}
public BaseComponent getTitleComponent() {
return titleComponent;
}
public NotificationBadgeBuilder setTitleComponent(BaseComponent titleComponent) {
this.titleComponent = titleComponent;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setTitleComponent(String titleText) {
this.titleComponent = new TextComponent(titleText);
this.packetDirty = true;
return this;
}
public BaseComponent getSourceComponent() {
return sourceComponent;
}
public NotificationBadgeBuilder setSourceComponent(BaseComponent sourceComponent) {
this.sourceComponent = sourceComponent;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setSourceComponent(String sourceText) {
this.sourceComponent = new TextComponent(sourceText);
this.packetDirty = true;
return this;
}
public long getOriginalTimestampSec() {
return originalTimestampSec;
}
public NotificationBadgeBuilder setOriginalTimestampSec(long originalTimestampSec) {
this.originalTimestampSec = originalTimestampSec;
this.packetDirty = true;
return this;
}
public boolean isSilent() {
return silent;
}
public NotificationBadgeBuilder setSilent(boolean silent) {
this.silent = silent;
this.packetDirty = true;
return this;
}
public BadgePriority getPriority() {
return priority;
}
public NotificationBadgeBuilder setPriority(BadgePriority priority) {
this.priority = priority;
this.packetDirty = true;
return this;
}
public UUID getMainIconUUID() {
return mainIconUUID;
}
public NotificationBadgeBuilder setMainIconUUID(UUID mainIconUUID) {
this.mainIconUUID = mainIconUUID;
this.packetDirty = true;
return this;
}
public UUID getTitleIconUUID() {
return titleIconUUID;
}
public NotificationBadgeBuilder setTitleIconUUID(UUID titleIconUUID) {
this.titleIconUUID = titleIconUUID;
this.packetDirty = true;
return this;
}
public int getHideAfterSec() {
return hideAfterSec;
}
public NotificationBadgeBuilder setHideAfterSec(int hideAfterSec) {
this.hideAfterSec = hideAfterSec;
this.packetDirty = true;
return this;
}
public int getExpireAfterSec() {
return expireAfterSec;
}
public NotificationBadgeBuilder setExpireAfterSec(int expireAfterSec) {
this.expireAfterSec = expireAfterSec;
this.packetDirty = true;
return this;
}
public int getBackgroundColor() {
return backgroundColor;
}
public NotificationBadgeBuilder setBackgroundColor(int backgroundColor) {
this.backgroundColor = backgroundColor;
this.packetDirty = true;
return this;
}
public int getBodyTxtColorRGB() {
return bodyTxtColor;
}
public NotificationBadgeBuilder setBodyTxtColorRGB(int colorRGB) {
this.bodyTxtColor = colorRGB;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setBodyTxtColorRGB(int colorR, int colorG, int colorB) {
this.bodyTxtColor = (colorR << 16) | (colorG << 8) | colorB;
this.packetDirty = true;
return this;
}
public int getTitleTxtColorRGB() {
return titleTxtColor;
}
public NotificationBadgeBuilder setTitleTxtColorRGB(int colorRGB) {
this.titleTxtColor = colorRGB;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setTitleTxtColorRGB(int colorR, int colorG, int colorB) {
this.titleTxtColor = (colorR << 16) | (colorG << 8) | colorB;
this.packetDirty = true;
return this;
}
public int getSourceTxtColorRGB() {
return sourceTxtColor;
}
public NotificationBadgeBuilder setSourceTxtColorRGB(int colorRGB) {
this.sourceTxtColor = colorRGB;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setSourceTxtColorRGB(int colorR, int colorG, int colorB) {
this.sourceTxtColor = (colorR << 16) | (colorG << 8) | colorB;
this.packetDirty = true;
return this;
}
public Object clone() {
return new NotificationBadgeBuilder(this);
}
public SPacketNotifBadgeShowV4EAG buildPacket() {
if(packetDirty || packetCache == null) {
if(badgeUUID == null) {
badgeUUID = UUID.randomUUID();
}else if(badgeUUID.getMostSignificantBits() == 0l && badgeUUID.getLeastSignificantBits() == 0l) {
throw new IllegalStateException("Badge UUID cannot be 0!");
}
EnumBadgePriority internalPriority;
switch(priority) {
case LOW:
default:
internalPriority = EnumBadgePriority.LOW;
break;
case NORMAL:
internalPriority = EnumBadgePriority.NORMAL;
break;
case HIGHER:
internalPriority = EnumBadgePriority.HIGHER;
break;
case HIGHEST:
internalPriority = EnumBadgePriority.HIGHEST;
break;
}
String bodyComp = bodyComponent != null ? ComponentSerializer.toString(bodyComponent) : "";
if(bodyComp.length() > 32767) {
throw new IllegalStateException("Body component is longer than 32767 chars serialized!");
}
String titleComp = titleComponent != null ? ComponentSerializer.toString(titleComponent) : "";
if(titleComp.length() > 255) {
throw new IllegalStateException("Title component is longer than 255 chars serialized!");
}
String sourceComp = sourceComponent != null ? ComponentSerializer.toString(sourceComponent) : "";
if(sourceComp.length() > 255) {
throw new IllegalStateException("Body component is longer than 255 chars serialized!");
}
packetCache = new SPacketNotifBadgeShowV4EAG(badgeUUID.getMostSignificantBits(),
badgeUUID.getLeastSignificantBits(), bodyComp, titleComp, sourceComp, originalTimestampSec, silent,
internalPriority, mainIconUUID != null ? mainIconUUID.getMostSignificantBits() : 0l,
mainIconUUID != null ? mainIconUUID.getLeastSignificantBits() : 0l,
titleIconUUID != null ? titleIconUUID.getMostSignificantBits() : 0l,
titleIconUUID != null ? titleIconUUID.getLeastSignificantBits() : 0l, hideAfterSec, expireAfterSec,
backgroundColor, bodyTxtColor, titleTxtColor, sourceTxtColor);
packetDirty = false;
}
return packetCache;
}
}

View File

@ -0,0 +1,92 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.net.InetAddress;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
/**
* 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 EaglercraftClientBrandEvent extends Event implements Cancellable {
private final String clientBrand;
private final String clientVersion;
private final String origin;
private final int protocolVersion;
private final InetAddress remoteAddress;
private boolean cancelled;
private BaseComponent message;
public EaglercraftClientBrandEvent(String clientBrand, String clientVersion, String origin, int protocolVersion,
InetAddress remoteAddress) {
this.clientBrand = clientBrand;
this.clientVersion = clientVersion;
this.origin = origin;
this.protocolVersion = protocolVersion;
this.remoteAddress = remoteAddress;
}
public BaseComponent getMessage() {
return message;
}
public void setMessage(BaseComponent message) {
this.message = message;
}
public String getClientBrand() {
return clientBrand;
}
public String getClientVersion() {
return clientVersion;
}
public String getOrigin() {
return origin;
}
public int getProtocolVersion() {
return protocolVersion;
}
public InetAddress getRemoteAddress() {
return remoteAddress;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
public void setKickMessage(String message) {
this.cancelled = true;
this.message = new TextComponent(message);
}
public void setKickMessage(BaseComponent message) {
this.cancelled = true;
this.message = message;
}
}

View File

@ -0,0 +1,206 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.function.Consumer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.md_5.bungee.api.plugin.Event;
/**
* Copyright (c) 2022-2023 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 EaglercraftHandleAuthCookieEvent extends Event {
public static enum AuthResponse {
ALLOW, DENY, REQUIRE_AUTH
}
private final EaglerListenerConfig listener;
private final InetAddress authRemoteAddress;
private final String authOrigin;
private final boolean enableCookies;
private final byte[] cookieData;
private final EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod;
private final String eventAuthMessage;
private final Object authAttachment;
private AuthResponse eventResponse;
private byte[] authUsername;
private String authProfileUsername;
private UUID authProfileUUID;
private String authRequestedServerRespose;
private String authDeniedMessage = "Bad Cookie!";
private String applyTexturesPropValue;
private String applyTexturesPropSignature;
private boolean overrideEaglerToVanillaSkins;
private Consumer<EaglercraftHandleAuthCookieEvent> continueThread;
private Runnable continueRunnable;
private volatile boolean hasContinue = false;
public EaglercraftHandleAuthCookieEvent(EaglerListenerConfig listener, InetAddress authRemoteAddress,
String authOrigin, byte[] authUsername, String authProfileUsername, UUID authProfileUUID,
boolean enableCookies, byte[] cookieData, EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod,
String eventAuthMessage, Object authAttachment, String authRequestedServerRespose,
Consumer<EaglercraftHandleAuthCookieEvent> continueThread) {
this.listener = listener;
this.authRemoteAddress = authRemoteAddress;
this.authOrigin = authOrigin;
this.authUsername = authUsername;
this.authProfileUsername = authProfileUsername;
this.authProfileUUID = authProfileUUID;
this.enableCookies = enableCookies;
this.cookieData = cookieData;
this.eventAuthMethod = eventAuthMethod;
this.eventAuthMessage = eventAuthMessage;
this.authAttachment = authAttachment;
this.authRequestedServerRespose = authRequestedServerRespose;
this.continueThread = continueThread;
}
public EaglerListenerConfig getListener() {
return listener;
}
public InetAddress getRemoteAddress() {
return authRemoteAddress;
}
public String getOriginHeader() {
return authOrigin;
}
public boolean getCookiesEnabled() {
return enableCookies;
}
public byte[] getCookieData() {
return cookieData;
}
public String getCookieDataString() {
return cookieData != null ? new String(cookieData, StandardCharsets.UTF_8) : null;
}
public byte[] getAuthUsername() {
return authUsername;
}
public String getProfileUsername() {
return authProfileUsername;
}
public void setProfileUsername(String username) {
this.authProfileUsername = username;
}
public UUID getProfileUUID() {
return authProfileUUID;
}
public void setProfileUUID(UUID uuid) {
this.authProfileUUID = uuid;
}
public EaglercraftIsAuthRequiredEvent.AuthMethod getAuthType() {
return eventAuthMethod;
}
public String getAuthMessage() {
return eventAuthMessage;
}
public <T> T getAuthAttachment() {
return (T)authAttachment;
}
public String getAuthRequestedServer() {
return authRequestedServerRespose;
}
public void setAuthRequestedServer(String server) {
this.authRequestedServerRespose = server;
}
public void setLoginAllowed() {
this.eventResponse = AuthResponse.ALLOW;
this.authDeniedMessage = null;
}
public void setLoginPasswordRequired() {
this.eventResponse = AuthResponse.REQUIRE_AUTH;
this.authDeniedMessage = null;
}
public void setLoginDenied(String message) {
this.eventResponse = AuthResponse.DENY;
this.authDeniedMessage = message;
}
public AuthResponse getLoginAllowed() {
return eventResponse;
}
public String getLoginDeniedMessage() {
return authDeniedMessage;
}
public Runnable makeAsyncContinue() {
if(continueRunnable == null) {
continueRunnable = new Runnable() {
@Override
public void run() {
if(!hasContinue) {
hasContinue = true;
continueThread.accept(EaglercraftHandleAuthCookieEvent.this);
}else {
throw new IllegalStateException("Thread was already continued from a different function! Auth plugin conflict?");
}
}
};
}
return continueRunnable;
}
public boolean isAsyncContinue() {
return continueRunnable != null;
}
public void doDirectContinue() {
continueThread.accept(this);
}
public void applyTexturesProperty(String value, String signature) {
applyTexturesPropValue = value;
applyTexturesPropSignature = signature;
}
public String getApplyTexturesPropertyValue() {
return applyTexturesPropValue;
}
public String getApplyTexturesPropertySignature() {
return applyTexturesPropSignature;
}
public void setOverrideEaglerToVanillaSkins(boolean overrideEaglerToVanillaSkins) {
this.overrideEaglerToVanillaSkins = overrideEaglerToVanillaSkins;
}
public boolean isOverrideEaglerToVanillaSkins() {
return overrideEaglerToVanillaSkins;
}
}

View File

@ -1,6 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.function.Consumer;
@ -29,17 +30,19 @@ public class EaglercraftHandleAuthPasswordEvent extends Event {
}
private final EaglerListenerConfig listener;
private final InetAddress authRemoteAddress;
private final String authOrigin;
private final InetAddress authRemoteAddress;
private final String authOrigin;
private final byte[] authUsername;
private final byte[] authSaltingData;
private final byte[] authPasswordData;
private final boolean enableCookies;
private final byte[] cookieData;
private final EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod;
private final String eventAuthMessage;
private final Object authAttachment;
private AuthResponse eventResponse;
private CharSequence authProfileUsername;
private String authProfileUsername;
private UUID authProfileUUID;
private String authRequestedServerRespose;
private String authDeniedMessage = "Password Incorrect!";
@ -51,10 +54,10 @@ public class EaglercraftHandleAuthPasswordEvent extends Event {
private volatile boolean hasContinue = false;
public EaglercraftHandleAuthPasswordEvent(EaglerListenerConfig listener, InetAddress authRemoteAddress,
String authOrigin, byte[] authUsername, byte[] authSaltingData, CharSequence authProfileUsername,
UUID authProfileUUID, byte[] authPasswordData, EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod,
String eventAuthMessage, Object authAttachment, String authRequestedServerRespose,
Consumer<EaglercraftHandleAuthPasswordEvent> continueThread) {
String authOrigin, byte[] authUsername, byte[] authSaltingData, String authProfileUsername,
UUID authProfileUUID, byte[] authPasswordData, boolean enableCookies, byte[] cookieData,
EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod, String eventAuthMessage, Object authAttachment,
String authRequestedServerRespose, Consumer<EaglercraftHandleAuthPasswordEvent> continueThread) {
this.listener = listener;
this.authRemoteAddress = authRemoteAddress;
this.authOrigin = authOrigin;
@ -63,6 +66,8 @@ public class EaglercraftHandleAuthPasswordEvent extends Event {
this.authProfileUsername = authProfileUsername;
this.authProfileUUID = authProfileUUID;
this.authPasswordData = authPasswordData;
this.enableCookies = enableCookies;
this.cookieData = cookieData;
this.eventAuthMethod = eventAuthMethod;
this.eventAuthMessage = eventAuthMessage;
this.authAttachment = authAttachment;
@ -90,11 +95,23 @@ public class EaglercraftHandleAuthPasswordEvent extends Event {
return authSaltingData;
}
public CharSequence getProfileUsername() {
public boolean getCookiesEnabled() {
return enableCookies;
}
public byte[] getCookieData() {
return cookieData;
}
public String getCookieDataString() {
return cookieData != null ? new String(cookieData, StandardCharsets.UTF_8) : null;
}
public String getProfileUsername() {
return authProfileUsername;
}
public void setProfileUsername(CharSequence username) {
public void setProfileUsername(String username) {
this.authProfileUsername = username;
}

View File

@ -7,7 +7,7 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerList
import net.md_5.bungee.api.plugin.Event;
/**
* 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
@ -43,7 +43,7 @@ public class EaglercraftIsAuthRequiredEvent extends Event {
}
private final EaglerListenerConfig listener;
private AuthResponse authResponse;
private AuthResponse authResponse;
private final InetAddress authRemoteAddress;
private final String authOrigin;
private final boolean wantsAuth;
@ -53,6 +53,7 @@ public class EaglercraftIsAuthRequiredEvent extends Event {
private String eventAuthMessage = "enter the code:";
private String kickUserMessage = "Login Denied";
private Object authAttachment;
private boolean enableCookieAuth;
private Consumer<EaglercraftIsAuthRequiredEvent> continueThread;
private Runnable continueRunnable;
private volatile boolean hasContinue = false;
@ -117,6 +118,14 @@ public class EaglercraftIsAuthRequiredEvent extends Event {
this.authAttachment = authAttachment;
}
public boolean getEnableCookieAuth() {
return enableCookieAuth;
}
public void setEnableCookieAuth(boolean enable) {
this.enableCookieAuth = enable;
}
public boolean shouldKickUser() {
return authResponse == null || authResponse == AuthResponse.DENY;
}

View File

@ -2,6 +2,7 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.md_5.bungee.api.plugin.Event;
/**
@ -21,13 +22,15 @@ import net.md_5.bungee.api.plugin.Event;
*/
public class EaglercraftRegisterCapeEvent extends Event {
private final Object authAttachment;
private final String username;
private final UUID uuid;
private byte[] customTex = null;
public EaglercraftRegisterCapeEvent(String username, UUID uuid) {
public EaglercraftRegisterCapeEvent(String username, UUID uuid, Object authAttachment) {
this.username = username;
this.uuid = uuid;
this.authAttachment = authAttachment;
}
public String getUsername() {
@ -47,10 +50,13 @@ public class EaglercraftRegisterCapeEvent extends Event {
customTex[4] = (byte)(p & 0xFF);
}
/**
* @param tex raw 32x32 pixel RGBA texture (4096 bytes long), see capes in "sources/resources/assets/eagler/capes"
*/
public void setForceUseCustom(byte[] tex) {
customTex = new byte[1 + tex.length];
customTex = new byte[1174];
customTex[0] = (byte)2;
System.arraycopy(tex, 0, customTex, 1, tex.length);
EaglerXBungeeAPIHelper.convertCape32x32RGBAto23x17RGB(tex, 0, customTex, 1);
}
public void setForceUseCustomByPacket(byte[] packet) {
@ -60,4 +66,8 @@ public class EaglercraftRegisterCapeEvent extends Event {
public byte[] getForceSetUseCustomPacket() {
return customTex;
}
public <T> T getAuthAttachment() {
return (T)authAttachment;
}
}

View File

@ -22,30 +22,29 @@ import net.md_5.bungee.protocol.Property;
*/
public class EaglercraftRegisterSkinEvent extends Event {
private final Object authAttachment;
private final String username;
private final UUID uuid;
private Property useMojangProfileProperty = null;
private boolean useLoginResultTextures = false;
private byte[] customTex = null;
private String customURL = null;
public EaglercraftRegisterSkinEvent(String username, UUID uuid) {
public EaglercraftRegisterSkinEvent(String username, UUID uuid, Object authAttachment) {
this.username = username;
this.uuid = uuid;
this.authAttachment = authAttachment;
}
public void setForceUseMojangProfileProperty(Property prop) {
useMojangProfileProperty = prop;
useLoginResultTextures = false;
customTex = null;
customURL = null;
}
public void setForceUseLoginResultObjectTextures(boolean b) {
useMojangProfileProperty = null;
useLoginResultTextures = b;
customTex = null;
customURL = null;
}
public void setForceUsePreset(int p) {
@ -57,9 +56,11 @@ public class EaglercraftRegisterSkinEvent extends Event {
customTex[2] = (byte)(p >>> 16);
customTex[3] = (byte)(p >>> 8);
customTex[4] = (byte)(p & 0xFF);
customURL = null;
}
/**
* @param tex raw 64x64 pixel RGBA texture (16384 bytes long)
*/
public void setForceUseCustom(int model, byte[] tex) {
useMojangProfileProperty = null;
useLoginResultTextures = false;
@ -67,21 +68,12 @@ public class EaglercraftRegisterSkinEvent extends Event {
customTex[0] = (byte)2;
customTex[1] = (byte)model;
System.arraycopy(tex, 0, customTex, 2, tex.length);
customURL = null;
}
public void setForceUseCustomByPacket(byte[] packet) {
useMojangProfileProperty = null;
useLoginResultTextures = false;
customTex = packet;
customURL = null;
}
public void setForceUseURL(String url) {
useMojangProfileProperty = null;
useLoginResultTextures = false;
customTex = null;
customURL = url;
}
public String getUsername() {
@ -104,8 +96,8 @@ public class EaglercraftRegisterSkinEvent extends Event {
return customTex;
}
public String getForceSetUseURL() {
return customURL;
public <T> T getAuthAttachment() {
return (T)authAttachment;
}
}

View File

@ -0,0 +1,91 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.RevokeSessionQueryHandler;
import net.md_5.bungee.api.plugin.Event;
/**
* 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 EaglercraftRevokeSessionQueryEvent extends Event {
private final InetAddress remoteAddress;
private final String origin;
private final byte[] cookieData;
private final RevokeSessionQueryHandler queryHandler;
private EnumSessionRevokeStatus revokeStatus;
private boolean shouldDeleteCookie;
public static enum EnumSessionRevokeStatus {
SUCCESS("ok", -1), FAILED_NOT_SUPPORTED("error", 1), FAILED_NOT_ALLOWED("error", 2),
FAILED_NOT_FOUND("error", 3), FAILED_SERVER_ERROR("error", 4);
public final String status;
public final int code;
private EnumSessionRevokeStatus(String status, int code) {
this.status = status;
this.code = code;
}
}
public EaglercraftRevokeSessionQueryEvent(InetAddress remoteAddress, String origin, byte[] cookieData,
RevokeSessionQueryHandler queryHandler) {
this.remoteAddress = remoteAddress;
this.origin = origin;
this.cookieData = cookieData;
this.queryHandler = queryHandler;
this.revokeStatus = EnumSessionRevokeStatus.FAILED_NOT_SUPPORTED;
this.shouldDeleteCookie = false;
}
public InetAddress getRemoteAddress() {
return remoteAddress;
}
public String getOrigin() {
return origin;
}
public byte[] getCookieData() {
return cookieData;
}
public String getCookieDataString() {
return new String(cookieData, StandardCharsets.UTF_8);
}
public RevokeSessionQueryHandler getQuery() {
return queryHandler;
}
public void setResultStatus(EnumSessionRevokeStatus revokeStatus) {
this.revokeStatus = revokeStatus;
}
public EnumSessionRevokeStatus getResultStatus() {
return revokeStatus;
}
public boolean getShouldDeleteCookie() {
return shouldDeleteCookie;
}
public void setShouldDeleteCookie(boolean b) {
this.shouldDeleteCookie = b;
}
}

View File

@ -0,0 +1,64 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;
/**
* 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 EaglercraftVoiceStatusChangeEvent extends Event {
public static enum EnumVoiceState {
SERVER_DISABLE, DISABLED, ENABLED;
}
private final ProxiedPlayer playerObj;
private final EaglerListenerConfig listener;
private final EaglerInitialHandler eaglerHandler;
private final EnumVoiceState voiceStateOld;
private final EnumVoiceState voiceStateNew;
public EaglercraftVoiceStatusChangeEvent(ProxiedPlayer playerObj, EaglerListenerConfig listener,
EaglerInitialHandler eaglerHandler, EnumVoiceState voiceStateOld, EnumVoiceState voiceStateNew) {
this.playerObj = playerObj;
this.listener = listener;
this.eaglerHandler = eaglerHandler;
this.voiceStateOld = voiceStateOld;
this.voiceStateNew = voiceStateNew;
}
public ProxiedPlayer getPlayerObj() {
return playerObj;
}
public EaglerListenerConfig getListener() {
return listener;
}
public EaglerInitialHandler getEaglerHandler() {
return eaglerHandler;
}
public EnumVoiceState getVoiceStateOld() {
return voiceStateOld;
}
public EnumVoiceState getVoiceStateNew() {
return voiceStateNew;
}
}

View File

@ -0,0 +1,70 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import io.netty.channel.Channel;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
/**
* 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 EaglercraftWebSocketOpenEvent extends Event implements Cancellable {
private final Channel channel;
private final EaglerListenerConfig listener;
private final String realIP;
private final String origin;
private final String userAgent;
private boolean cancelled = false;
public EaglercraftWebSocketOpenEvent(Channel channel, EaglerListenerConfig listener, String realIP, String origin, String userAgent) {
this.channel = channel;
this.listener = listener;
this.realIP = realIP;
this.origin = origin;
this.userAgent = userAgent;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean var1) {
cancelled = var1;
}
public Channel getChannel() {
return channel;
}
public EaglerListenerConfig getListener() {
return listener;
}
public String getRealIP() {
return realIP;
}
public String getOrigin() {
return origin;
}
public String getUserAgent() {
return userAgent;
}
}

View File

@ -0,0 +1,56 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;
/**
* 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 EaglercraftWebViewChannelEvent extends Event {
public static enum EventType {
CHANNEL_OPEN, CHANNEL_CLOSE;
}
private final ProxiedPlayer player;
private final EaglerListenerConfig listener;
private final String channel;
private final EventType type;
public EaglercraftWebViewChannelEvent(ProxiedPlayer player, EaglerListenerConfig listener, String channel, EventType type) {
this.player = player;
this.listener = listener;
this.channel = channel;
this.type = type;
}
public ProxiedPlayer getPlayer() {
return player;
}
public EaglerListenerConfig getListener() {
return listener;
}
public String getChannel() {
return channel;
}
public EventType getType() {
return type;
}
}

View File

@ -0,0 +1,118 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketWebViewMessageV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketWebViewMessageV4EAG;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;
/**
* 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 EaglercraftWebViewMessageEvent extends Event {
public static enum MessageType {
STRING(SPacketWebViewMessageV4EAG.TYPE_STRING), BINARY(SPacketWebViewMessageV4EAG.TYPE_BINARY);
private final int id;
private MessageType(int id) {
this.id = id;
}
private static MessageType fromId(int id) {
switch(id) {
case CPacketWebViewMessageV4EAG.TYPE_STRING:
return STRING;
default:
case CPacketWebViewMessageV4EAG.TYPE_BINARY:
return BINARY;
}
}
}
private final ProxiedPlayer player;
private final EaglerListenerConfig listener;
private final String currentChannel;
private final EaglerInitialHandler eaglerHandler;
private final MessageType type;
private final byte[] data;
private String asString;
public EaglercraftWebViewMessageEvent(ProxiedPlayer player, EaglerListenerConfig listener, String currentChannel, MessageType type, byte[] data) {
this.player = player;
this.listener = listener;
this.currentChannel = currentChannel;
this.eaglerHandler = EaglerXBungeeAPIHelper.getEaglerHandle(player);
this.type = type;
this.data = data;
}
public EaglercraftWebViewMessageEvent(ProxiedPlayer player, EaglerListenerConfig listener, String currentChannel, CPacketWebViewMessageV4EAG packet) {
this.player = player;
this.listener = listener;
this.currentChannel = currentChannel;
this.eaglerHandler = EaglerXBungeeAPIHelper.getEaglerHandle(player);
this.type = MessageType.fromId(packet.type);
this.data = packet.data;
}
public ProxiedPlayer getPlayer() {
return player;
}
public EaglerListenerConfig getListener() {
return listener;
}
public EaglerInitialHandler getEaglerHandle() {
return eaglerHandler;
}
public void sendResponse(MessageType type, byte[] data) {
eaglerHandler.sendEaglerMessage(new SPacketWebViewMessageV4EAG(type.id, data));
}
public void sendResponse(String str) {
sendResponse(MessageType.STRING, str.getBytes(StandardCharsets.UTF_8));
}
public void sendResponse(byte[] data) {
sendResponse(MessageType.BINARY, data);
}
public MessageType getType() {
return type;
}
public byte[] getAsBinary() {
return data;
}
public String getAsString() {
if(asString == null) {
asString = new String(data, StandardCharsets.UTF_8);
}
return asString;
}
public String getChannelName() {
return currentChannel;
}
}

View File

@ -3,6 +3,7 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query;
import java.net.InetAddress;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
/**
@ -31,7 +32,7 @@ public interface MOTDConnection {
long getConnectionTimestamp();
public default long getConnectionAge() {
return System.currentTimeMillis() - getConnectionTimestamp();
return EaglerXBungeeAPIHelper.steadyTimeMillis() - getConnectionTimestamp();
}
void sendToUser();

View File

@ -5,6 +5,8 @@ import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
*
@ -28,7 +30,7 @@ public class AuthLoadingCache<K, V> {
private V instance;
private CacheEntry(V instance) {
this.lastHit = System.currentTimeMillis();
this.lastHit = EaglerXBungeeAPIHelper.steadyTimeMillis();
this.instance = instance;
}
@ -49,7 +51,7 @@ public class AuthLoadingCache<K, V> {
private long cacheTimer;
public AuthLoadingCache(CacheLoader<K, V> provider, long cacheTTL) {
this.cacheMap = new HashMap();
this.cacheMap = new HashMap<>();
this.provider = provider;
this.cacheTTL = cacheTTL;
}
@ -66,7 +68,7 @@ public class AuthLoadingCache<K, V> {
}
return loaded;
}else {
etr.lastHit = System.currentTimeMillis();
etr.lastHit = EaglerXBungeeAPIHelper.steadyTimeMillis();
return etr.instance;
}
}
@ -90,7 +92,7 @@ public class AuthLoadingCache<K, V> {
}
public void tick() {
long millis = System.currentTimeMillis();
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
if(millis - cacheTimer > (cacheTTL / 2L)) {
cacheTimer = millis;
synchronized(cacheMap) {

View File

@ -24,6 +24,7 @@ import java.util.UUID;
import java.util.logging.Level;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftHandleAuthPasswordEvent;
@ -249,7 +250,7 @@ public class DefaultAuthSystem {
this.checkRegistrationByName = databaseConnection.prepareStatement("SELECT Version, MojangUUID, MojangTextures, HashBase, HashSalt, Registered, RegisteredIP, LastLogin, LastLoginIP FROM eaglercraft_accounts WHERE MojangUsername = ?");
this.setLastLogin = databaseConnection.prepareStatement("UPDATE eaglercraft_accounts SET LastLogin = ?, LastLoginIP = ? WHERE MojangUUID = ?");
this.updateTextures = databaseConnection.prepareStatement("UPDATE eaglercraft_accounts SET MojangTextures = ? WHERE MojangUUID = ?");
this.authLoadingCache = new AuthLoadingCache(new AccountLoader(), 120000l);
this.authLoadingCache = new AuthLoadingCache<>(new AccountLoader(), 120000l);
this.secureRandom = new SecureRandom();
}
@ -473,7 +474,7 @@ public class DefaultAuthSystem {
Property prop = props[i];
if("textures".equals(prop.getName())) {
byte[] texturesData = Base64.decodeBase64(prop.getValue());
byte[] signatureData = prop.getSignature() == null ? new byte[0] : Base64.decodeBase64(prop.getSignature());
byte[] signatureData = prop.getSignature() == null ? ArrayUtils.EMPTY_BYTE_ARRAY : Base64.decodeBase64(prop.getSignature());
ByteArrayOutputStream bao = new ByteArrayOutputStream();
DataOutputStream dao = new DataOutputStream(bao);
dao.writeInt(texturesData.length);

View File

@ -0,0 +1,93 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command;
import io.netty.buffer.Unpooled;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.OverflowPacketException;
import net.md_5.bungee.protocol.packet.PluginMessage;
/**
* 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 CommandClientBrand extends Command {
public CommandClientBrand() {
super("client-brand", "eaglercraft.command.clientbrand", "clientbrand");
}
@Override
public void execute(CommandSender var1, String[] var2) {
if(var2.length == 1) {
ProxiedPlayer player = BungeeCord.getInstance().getPlayer(var2[0]);
if(player != null) {
if(player.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler handler = (EaglerInitialHandler)player.getPendingConnection();
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client Brand: " + ChatColor.WHITE + handler.getEaglerBrandString()));
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client Version: " + ChatColor.WHITE + handler.getEaglerVersionString()));
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client UUID: " + ChatColor.WHITE + handler.getClientBrandUUID()));
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Minecraft Client Brand: " + ChatColor.WHITE + decodeMCBrand(handler.getBrandMessage())));
}else {
var1.sendMessage(new TextComponent(ChatColor.RED + "That player is not using eaglercraft!"));
}
}else {
var1.sendMessage(new TextComponent(ChatColor.RED + "That player was not found!"));
}
return;
}
if(var2.length == 2) {
ProxiedPlayer player = BungeeCord.getInstance().getPlayer(var2[1]);
if(player != null) {
if(player.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler handler = (EaglerInitialHandler)player.getPendingConnection();
if("uuid".equalsIgnoreCase(var2[0])) {
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client UUID: " + ChatColor.WHITE + handler.getClientBrandUUID()));
return;
}else if("name".equalsIgnoreCase(var2[0])) {
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client Brand: " + ChatColor.WHITE + handler.getEaglerBrandString()));
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Eagler Client Version: " + ChatColor.WHITE + handler.getEaglerVersionString()));
return;
}else if("mc".equalsIgnoreCase(var2[0])) {
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Minecraft Client Brand: " + ChatColor.WHITE + decodeMCBrand(handler.getBrandMessage())));
return;
}
}else {
var1.sendMessage(new TextComponent(ChatColor.RED + "That player is not using eaglercraft!"));
return;
}
}else {
var1.sendMessage(new TextComponent(ChatColor.RED + "That player was not found!"));
return;
}
}
var1.sendMessage(new TextComponent(ChatColor.RED + "Usage: /client-brand [uuid|name|mc] <username>"));
}
private static String decodeMCBrand(PluginMessage pkt) {
if(pkt == null) {
return "null";
}
try {
return DefinedPacket.readString(Unpooled.wrappedBuffer(pkt.getData()), 64);
}catch(OverflowPacketException | IndexOutOfBoundsException ex) {
return "null";
}
}
}

View File

@ -45,7 +45,7 @@ public class CommandDomain extends Command {
var1.sendMessage(new TextComponent(ChatColor.RED + "That user is not using Eaglercraft"));
return;
}
String origin = ((EaglerInitialHandler)conn).origin;
String origin = ((EaglerInitialHandler)conn).getOrigin();
if(origin != null) {
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Domain of " + var2[0] + " is '" + origin + "'"));
}else {

View File

@ -3,10 +3,12 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@ -14,6 +16,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@ -26,6 +29,7 @@ import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpContentType;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
@ -50,7 +54,7 @@ import net.md_5.bungee.protocol.Property;
public class EaglerBungeeConfig {
public static EaglerBungeeConfig loadConfig(File directory) throws IOException {
Map<String, HttpContentType> contentTypes = new HashMap();
Map<String, HttpContentType> contentTypes = new HashMap<>();
try(InputStream is = new FileInputStream(getConfigFile(directory, "http_mime_types.json"))) {
loadMimeTypes(is, contentTypes);
@ -68,6 +72,7 @@ public class EaglerBungeeConfig {
Configuration configYml = prov.load(getConfigFile(directory, "settings.yml"));
String serverName = configYml.getString("server_name", "EaglercraftXBungee Server");
EaglerXBungeeAPIHelper.getTemplateGlobals().put("server_name", serverName);
String serverUUIDString = configYml.getString("server_uuid", null);
if(serverUUIDString == null) {
throw new IOException("You must specify a server_uuid!");
@ -85,7 +90,7 @@ public class EaglerBungeeConfig {
Configuration listenerYml = prov.load(getConfigFile(directory, "listeners.yml"));
Iterator<String> listeners = listenerYml.getKeys().iterator();
Map<String, EaglerListenerConfig> serverListeners = new HashMap();
Map<String, EaglerListenerConfig> serverListeners = new HashMap<>();
boolean voiceChat = false;
while(listeners.hasNext()) {
@ -119,6 +124,43 @@ public class EaglerBungeeConfig {
}
}
File pauseMenuFolder = new File(directory, "pause_menu");
if(!pauseMenuFolder.isDirectory() && !pauseMenuFolder.mkdir()) {
throw new IOException("Could not create directory: " + pauseMenuFolder.getAbsolutePath());
}
File pauseMenuYml = new File(pauseMenuFolder, "pause_menu.yml");
if(!pauseMenuYml.isFile()) {
try(InputStream is = EaglerBungeeConfig.class.getResourceAsStream("default_pause_menu.yml")) {
copyConfigFile(is, pauseMenuYml);
}
File f2 = new File(pauseMenuFolder, "server_info.html");
if(!f2.isFile()) {
try(InputStream is = EaglerBungeeConfig.class.getResourceAsStream("default_pause_menu_server_info.html")) {
copyConfigFile(is, f2);
}
}
f2 = new File(pauseMenuFolder, "test_image.png");
if(!f2.isFile()) {
try(InputStream is = EaglerBungeeConfig.class.getResourceAsStream("default_pause_menu_test_image.png")) {
copyBinaryFile(is, f2);
}
}
f2 = new File(pauseMenuFolder, "message_api_example.html");
if(!f2.isFile()) {
try(InputStream is = EaglerBungeeConfig.class.getResourceAsStream("default_message_api_example.html")) {
copyConfigFile(is, f2);
}
}
f2 = new File(pauseMenuFolder, "message_api_v1.js");
if(!f2.isFile()) {
try(InputStream is = EaglerBungeeConfig.class.getResourceAsStream("default_message_api_v1.js")) {
copyConfigFile(is, f2);
}
}
}
EaglerPauseMenuConfig pauseMenuConfig = EaglerPauseMenuConfig.loadConfig(prov.load(pauseMenuYml), pauseMenuFolder);
long websocketKeepAliveTimeout = configYml.getInt("websocket_connection_timeout", 15000);
long websocketHandshakeTimeout = configYml.getInt("websocket_handshake_timeout", 5000);
long builtinHttpServerTimeout = configYml.getInt("builtin_http_server_timeout", 10000);
@ -144,9 +186,10 @@ public class EaglerBungeeConfig {
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"));
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"));
Set<String> disableFNAWSkinsOnServers = new HashSet<>((Collection<String>)configYml.getList("disable_fnaw_skins_on_servers"));
boolean enableBackendRPCAPI = configYml.getBoolean("enable_backend_rpc_api", false);
final EaglerBungeeConfig ret = new EaglerBungeeConfig(serverName, serverUUID, websocketKeepAliveTimeout,
websocketHandshakeTimeout, builtinHttpServerTimeout, websocketCompressionLevel, serverListeners,
@ -154,7 +197,7 @@ public class EaglerBungeeConfig {
skinRateLimitPlayer, skinRateLimitGlobal, skinCacheURI, keepObjectsDays, keepProfilesDays, maxObjects,
maxProfiles, antagonistsRateLimit, sqliteDriverClass, sqliteDriverPath, eaglerPlayersVanillaSkin,
enableIsEaglerPlayerProperty, authConfig, updatesConfig, iceServers, voiceChat, disableVoiceOnServers,
disableFNAWSkinsEverywhere, disableFNAWSkinsOnServers);
disableFNAWSkinsEverywhere, disableFNAWSkinsOnServers, enableBackendRPCAPI, pauseMenuConfig);
if(eaglerPlayersVanillaSkin != null) {
VanillaDefaultSkinProfileLoader.lookupVanillaSkinUser(ret);
@ -168,7 +211,7 @@ public class EaglerBungeeConfig {
if(!file.isFile()) {
try (BufferedReader is = new BufferedReader(new InputStreamReader(
EaglerBungeeConfig.class.getResourceAsStream("default_" + fileName), StandardCharsets.UTF_8));
PrintWriter os = new PrintWriter(new FileWriter(file))) {
PrintWriter os = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
String line;
while((line = is.readLine()) != null) {
if(line.contains("${")) {
@ -181,6 +224,26 @@ public class EaglerBungeeConfig {
return file;
}
private static void copyConfigFile(InputStream is, File file) throws IOException {
try(PrintWriter os = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
String line;
while((line = reader.readLine()) != null) {
os.println(line);
}
}
}
private static void copyBinaryFile(InputStream is, File file) throws IOException {
try(OutputStream os = new FileOutputStream(file)) {
byte[] copyBuffer = new byte[1024];
int i;
while((i = is.read(copyBuffer)) != -1) {
os.write(copyBuffer, 0, i);
}
}
}
private static void loadMimeTypes(InputStream file, Map<String, HttpContentType> contentTypes) throws IOException {
JsonObject obj = parseJsonObject(file);
for(Entry<String, JsonElement> etr : obj.entrySet()) {
@ -192,7 +255,7 @@ public class EaglerBungeeConfig {
EaglerXBungee.logger().warning("MIME type '" + mime + "' defines no extensions!");
continue;
}
HashSet<String> exts = new HashSet();
HashSet<String> exts = new HashSet<>();
for(int i = 0, l = arr.size(); i < l; ++i) {
exts.add(arr.get(i).getAsString());
}
@ -217,8 +280,8 @@ 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");
Collection<String> ret = new ArrayList<>(config.contains("voice_stun_servers") ? (List<String>)config.getList("voice_stun_servers") : (List<String>)config.getList("voice_servers_no_passwd"));
Configuration turnServers = config.contains("voice_turn_servers") ? config.getSection("voice_turn_servers") : config.getSection("voice_servers_passwd");
Iterator<String> turnItr = turnServers.getKeys().iterator();
while(turnItr.hasNext()) {
String name = turnItr.next();
@ -269,6 +332,8 @@ public class EaglerBungeeConfig {
private final Set<String> disableVoiceOnServers;
private final boolean disableFNAWSkinsEverywhere;
private final Set<String> disableFNAWSkinsOnServers;
private final boolean enableBackendRPCAPI;
private final EaglerPauseMenuConfig pauseMenuConf;
private boolean isCrackedFlag;
Property[] eaglerPlayersVanillaSkinCached = new Property[] { isEaglerProperty };
@ -435,6 +500,14 @@ public class EaglerBungeeConfig {
return disableFNAWSkinsOnServers;
}
public boolean getEnableBackendRPCAPI() {
return enableBackendRPCAPI;
}
public EaglerPauseMenuConfig getPauseMenuConf() {
return pauseMenuConf;
}
private EaglerBungeeConfig(String serverName, UUID serverUUID, long websocketKeepAliveTimeout,
long websocketHandshakeTimeout, long builtinHttpServerTimeout, int httpWebsocketCompressionLevel,
Map<String, EaglerListenerConfig> serverListeners, Map<String, HttpContentType> contentTypes,
@ -444,7 +517,8 @@ public class EaglerBungeeConfig {
String sqliteDriverClass, String sqliteDriverPath, String eaglerPlayersVanillaSkin,
boolean enableIsEaglerPlayerProperty, EaglerAuthConfig authConfig, EaglerUpdateConfig updateConfig,
Collection<String> iceServers, boolean enableVoiceChat, Set<String> disableVoiceOnServers,
boolean disableFNAWSkinsEverywhere, Set<String> disableFNAWSkinsOnServers) {
boolean disableFNAWSkinsEverywhere, Set<String> disableFNAWSkinsOnServers,
boolean enableBackendRPCAPI, EaglerPauseMenuConfig pauseMenuConf) {
this.serverName = serverName;
this.serverUUID = serverUUID;
this.serverListeners = serverListeners;
@ -476,6 +550,8 @@ public class EaglerBungeeConfig {
this.disableVoiceOnServers = disableVoiceOnServers;
this.disableFNAWSkinsEverywhere = disableFNAWSkinsEverywhere;
this.disableFNAWSkinsOnServers = disableFNAWSkinsOnServers;
this.enableBackendRPCAPI = enableBackendRPCAPI;
this.pauseMenuConf = pauseMenuConf;
}
}

View File

@ -74,8 +74,14 @@ public class EaglerListenerConfig extends ListenerInfo {
for(int i = 0, l = serverMOTD.size(); i < l; ++i) {
serverMOTD.set(i, ChatColor.translateAlternateColorCodes('&', serverMOTD.get(i)));
}
boolean allowMOTD = config.getBoolean("allow_motd", false);
boolean allowQuery = config.getBoolean("allow_query", false);
boolean allowMOTD = config.getBoolean("allow_motd", true);
boolean allowQuery = config.getBoolean("allow_query", true);
boolean allowV3 = config.getBoolean("allow_protocol_v3", true);
boolean allowV4 = config.getBoolean("allow_protocol_v4", true);
if(!allowV3 && !allowV4) {
throw new IllegalArgumentException("Both v3 and v4 protocol are disabled!");
}
int defragSendDelay = config.getInt("protocol_v4_defrag_send_delay", 10);
int cacheTTL = 7200;
boolean cacheAnimation = false;
@ -102,8 +108,8 @@ public class EaglerListenerConfig extends ListenerInfo {
page404 = null;
}
List<String> defaultIndex = Arrays.asList("index.html", "index.htm");
List indexPageRaw = httpServerConf.getList("page_index_name", defaultIndex);
List<String> indexPage = new ArrayList(indexPageRaw.size());
List<?> indexPageRaw = httpServerConf.getList("page_index_name", defaultIndex);
List<String> indexPage = new ArrayList<>(indexPageRaw.size());
for(int i = 0, l = indexPageRaw.size(); i < l; ++i) {
Object o = indexPageRaw.get(i);
@ -151,7 +157,8 @@ 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, enableVoiceChat, ratelimitIp, ratelimitLogin, ratelimitMOTD, ratelimitQuery);
allowV3, allowV4, defragSendDelay, cacheConfig, httpServer, enableVoiceChat, ratelimitIp,
ratelimitLogin, ratelimitMOTD, ratelimitQuery);
}
private final InetSocketAddress address;
@ -167,6 +174,9 @@ public class EaglerListenerConfig extends ListenerInfo {
private final List<String> serverMOTD;
private final boolean allowMOTD;
private final boolean allowQuery;
private final boolean allowV3;
private final boolean allowV4;
private final int defragSendDelay;
private final MOTDCacheConfiguration motdCacheConfig;
private final HttpWebServer webServer;
private boolean serverIconSet = false;
@ -180,9 +190,10 @@ public class EaglerListenerConfig extends ListenerInfo {
public EaglerListenerConfig(InetSocketAddress address, InetSocketAddress addressV6, int maxPlayer,
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,
boolean enableVoiceChat, EaglerRateLimiter ratelimitIp, EaglerRateLimiter ratelimitLogin,
EaglerRateLimiter ratelimitMOTD, EaglerRateLimiter ratelimitQuery) {
boolean allowMOTD, boolean allowQuery, boolean allowV3, boolean allowV4, int defragSendDelay,
MOTDCacheConfiguration motdCacheConfig, HttpWebServer webServer, 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;
@ -198,6 +209,9 @@ public class EaglerListenerConfig extends ListenerInfo {
this.serverMOTD = serverMOTD;
this.allowMOTD = allowMOTD;
this.allowQuery = allowQuery;
this.allowV3 = allowV3;
this.allowV4 = allowV4;
this.defragSendDelay = defragSendDelay;
this.motdCacheConfig = motdCacheConfig;
this.webServer = webServer;
this.enableVoiceChat = enableVoiceChat;
@ -272,7 +286,19 @@ public class EaglerListenerConfig extends ListenerInfo {
public boolean isAllowQuery() {
return allowQuery;
}
public boolean isAllowV3() {
return allowV3;
}
public boolean isAllowV4() {
return allowV4;
}
public int getDefragSendDelay() {
return defragSendDelay;
}
public HttpWebServer getWebServer() {
return webServer;
}

View File

@ -0,0 +1,251 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.WeakHashMap;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketCustomizePauseMenuV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketServerInfoDataChunkV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.PacketImageData;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SimpleOutputBufferImpl;
import net.md_5.bungee.config.Configuration;
/**
* 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 EaglerPauseMenuConfig {
private boolean enableCustomPauseMenu;
private SPacketCustomizePauseMenuV4EAG customPauseMenuPacket;
private byte[] serverInfoHash;
private List<SPacketServerInfoDataChunkV4EAG> serverInfoChunks;
private int infoSendRate;
static EaglerPauseMenuConfig loadConfig(Configuration conf, File baseDir) throws IOException {
boolean enabled = conf.getBoolean("enable_custom_pause_menu", false);
if(!enabled) {
return new EaglerPauseMenuConfig(false, null, null, 1);
}
Configuration server_info_button = conf.getSection("server_info_button");
boolean enableInfoButton = server_info_button.getBoolean("enable_button", false);
String infoButtonText = server_info_button.getString("button_text", "Server Info");
boolean infoButtonModeNewTab = server_info_button.getBoolean("button_mode_open_new_tab", false);
String infoButtonEmbedURL = server_info_button.getString("server_info_embed_url", "");
boolean infoButtonModeEmbedFile = server_info_button.getBoolean("button_mode_embed_file", true);
String infoButtonEmbedFile = server_info_button.getString("server_info_embed_file", "server_info.html");
String infoButtonEmbedScreenTitle = server_info_button.getString("server_info_embed_screen_title", "Server Info");
int infoSendRate = server_info_button.getInt("server_info_embed_send_chunk_rate", 1);
int infoChunkSize = server_info_button.getInt("server_info_embed_send_chunk_size", 24576);
if(infoChunkSize > 32720) {
throw new IOException("Chunk size " +infoChunkSize + " is too large! Max is 32720 bytes");
}
boolean infoButtonEnableTemplateMacros = server_info_button.getBoolean("enable_template_macros", true);
Configuration globals = server_info_button.getSection("server_info_embed_template_globals");
for(String s : globals.getKeys()) {
EaglerXBungeeAPIHelper.getTemplateGlobals().put(s, globals.getString(s));
}
boolean infoButtonAllowTemplateEvalMacro = server_info_button.getBoolean("allow_embed_template_eval_macro", false);
boolean infoButtonEnableWebviewJavascript = server_info_button.getBoolean("enable_webview_javascript", false);
boolean infoButtonEnableWebviewMessageAPI = server_info_button.getBoolean("enable_webview_message_api", false);
boolean infoButtonEnableWebviewStrictCSP = server_info_button.getBoolean("enable_webview_strict_csp", true);
Configuration discord_button = conf.getSection("discord_button");
boolean enableDiscordButton = discord_button.getBoolean("enable_button", false);
String discordButtonText = discord_button.getString("button_text", "Discord");
String discordButtonURL = discord_button.getString("button_url", "https://invite url here");
int infoButtonMode = enableInfoButton
? (infoButtonModeEmbedFile
? (infoButtonEmbedFile.length() > 0
? SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_SHOW_EMBED_OVER_WS
: SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_NONE)
: (infoButtonEmbedURL.length() > 0
? (infoButtonModeNewTab ? SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_EXTERNAL_URL
: SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_SHOW_EMBED_OVER_HTTP)
: SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_NONE))
: SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_NONE;
int discordButtonMode = (enableDiscordButton && discordButtonURL.length() > 0)
? SPacketCustomizePauseMenuV4EAG.DISCORD_MODE_INVITE_URL
: SPacketCustomizePauseMenuV4EAG.DISCORD_MODE_NONE;
int webviewPerms = (infoButtonEnableWebviewJavascript ? SPacketCustomizePauseMenuV4EAG.SERVER_INFO_EMBED_PERMS_JAVASCRIPT : 0) |
(infoButtonEnableWebviewMessageAPI ? SPacketCustomizePauseMenuV4EAG.SERVER_INFO_EMBED_PERMS_MESSAGE_API : 0) |
(infoButtonEnableWebviewStrictCSP ? SPacketCustomizePauseMenuV4EAG.SERVER_INFO_EMBED_PERMS_STRICT_CSP : 0);
Map<String,String> imagesToActuallyLoad = new WeakHashMap<>();
Configuration custom_images = conf.getSection("custom_images");
for(String s : custom_images.getKeys()) {
String fileName = custom_images.getString(s, "");
if(fileName.length() > 0) {
imagesToActuallyLoad.put(s, fileName);
}
}
Map<String,Integer> imageMappings = null;
List<PacketImageData> customImageDatas = null;
if(imagesToActuallyLoad.size() > 0) {
Map<String,PacketImageData> imageLoadingCache = new HashMap<>();
TIntObjectMap<PacketImageData> imageDumbHashTable = new TIntObjectHashMap<>();
imageMappings = new HashMap<>();
customImageDatas = new ArrayList<>();
outer_loop: for(Entry<String,String> etr : imagesToActuallyLoad.entrySet()) {
String key = etr.getKey();
String value = etr.getValue();
PacketImageData existing = imageLoadingCache.get(value);
if(existing != null) {
for(int i = 0, l = customImageDatas.size(); i < l; ++i) {
if(customImageDatas.get(i) == existing) {
imageMappings.put(key, i);
continue outer_loop;
}
}
imageMappings.put(key, customImageDatas.size());
customImageDatas.add(existing);
continue outer_loop;
}else {
PacketImageData img = EaglerXBungeeAPIHelper.loadPacketImageData(new File(baseDir, value), 64, 64);
int hashCode = Arrays.hashCode(img.rgba);
PacketImageData possibleClone = imageDumbHashTable.get(hashCode);
if (possibleClone != null && possibleClone.width == img.width && possibleClone.height == img.height
&& Arrays.equals(img.rgba, possibleClone.rgba)) {
for(int i = 0, l = customImageDatas.size(); i < l; ++i) {
if(customImageDatas.get(i) == possibleClone) {
imageMappings.put(key, i);
continue outer_loop;
}
}
imageMappings.put(key, customImageDatas.size());
customImageDatas.add(possibleClone);
continue outer_loop;
}else {
imageMappings.put(key, customImageDatas.size());
customImageDatas.add(img);
imageDumbHashTable.put(hashCode, img);
continue outer_loop;
}
}
}
}
SPacketCustomizePauseMenuV4EAG pausePacket = new SPacketCustomizePauseMenuV4EAG();
List<SPacketServerInfoDataChunkV4EAG> serverInfoChunks = null;
pausePacket.serverInfoMode = infoButtonMode;
switch(infoButtonMode) {
case SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_NONE:
default:
break;
case SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_EXTERNAL_URL:
pausePacket.serverInfoButtonText = infoButtonText;
pausePacket.serverInfoURL = infoButtonEmbedURL;
break;
case SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_SHOW_EMBED_OVER_HTTP:
pausePacket.serverInfoButtonText = infoButtonText;
pausePacket.serverInfoURL = infoButtonEmbedURL;
pausePacket.serverInfoEmbedPerms = webviewPerms;
pausePacket.serverInfoEmbedTitle = infoButtonEmbedScreenTitle;
break;
case SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_SHOW_EMBED_OVER_WS:
pausePacket.serverInfoButtonText = infoButtonText;
byte[] hash = new byte[20];
String rawData = EaglerXBungeeAPIHelper.loadFileToStringServerInfo(new File(baseDir, infoButtonEmbedFile));
if(infoButtonEnableTemplateMacros) {
rawData = EaglerXBungeeAPIHelper.loadServerInfoTemplateEagler(rawData, baseDir, infoButtonAllowTemplateEvalMacro);
}
serverInfoChunks = EaglerXBungeeAPIHelper.convertServerInfoToChunks(rawData.getBytes(StandardCharsets.UTF_8), hash, infoChunkSize);
if(!serverInfoChunks.isEmpty()) {
SPacketServerInfoDataChunkV4EAG pk = serverInfoChunks.get(0);
EaglerXBungee.logger().info("Total server info embed size: " + pk.finalSize + " bytes" + (serverInfoChunks.size() > 1 ? (" (" + serverInfoChunks.size() + " chunks)") : ""));
}
pausePacket.serverInfoEmbedPerms = webviewPerms;
pausePacket.serverInfoEmbedTitle = infoButtonEmbedScreenTitle;
pausePacket.serverInfoHash = hash;
break;
}
pausePacket.discordButtonMode = discordButtonMode;
switch(discordButtonMode) {
case SPacketCustomizePauseMenuV4EAG.DISCORD_MODE_NONE:
default:
break;
case SPacketCustomizePauseMenuV4EAG.DISCORD_MODE_INVITE_URL:
pausePacket.discordButtonMode = discordButtonMode;
pausePacket.discordButtonText = discordButtonText;
pausePacket.discordInviteURL = discordButtonURL;
break;
}
pausePacket.imageMappings = imageMappings;
pausePacket.imageData = customImageDatas;
SimpleOutputBufferImpl ob = new SimpleOutputBufferImpl(new TestOutputStream());
pausePacket.writePacket(ob);
int cnt = ob.size();
EaglerXBungee.logger().info("Total pause menu packet size: " + cnt + " bytes");
if(cnt > 32760) {
throw new IOException("Pause menu packet is " + (cnt - 32760) + " bytes too large! Try making the images smaller or reusing the same image file for multiple icons!");
}
return new EaglerPauseMenuConfig(enabled, pausePacket, serverInfoChunks, infoSendRate);
}
private EaglerPauseMenuConfig(boolean enableCustomPauseMenu, SPacketCustomizePauseMenuV4EAG customPauseMenuPacket,
List<SPacketServerInfoDataChunkV4EAG> serverInfoChunks, int infoSendRate) {
this.enableCustomPauseMenu = enableCustomPauseMenu;
this.customPauseMenuPacket = customPauseMenuPacket;
this.serverInfoHash = customPauseMenuPacket != null ? customPauseMenuPacket.serverInfoHash : null;
this.serverInfoChunks = serverInfoChunks;
this.infoSendRate = infoSendRate;
}
public boolean getEnabled() {
return enableCustomPauseMenu;
}
public SPacketCustomizePauseMenuV4EAG getPacket() {
return customPauseMenuPacket;
}
public byte[] getServerInfoHash() {
return serverInfoHash;
}
public List<SPacketServerInfoDataChunkV4EAG> getServerInfo() {
return serverInfoChunks;
}
public int getInfoSendRate() {
return infoSendRate;
}
}

View File

@ -7,6 +7,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.md_5.bungee.config.Configuration;
/**
@ -95,7 +96,7 @@ public class EaglerRateLimiter {
protected long cooldownTimestamp = 0l;
protected RateLimitStatus rateLimit() {
long millis = System.currentTimeMillis();
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
tick(millis);
if(lockoutTimestamp != 0l) {
return RateLimitStatus.LOCKED_OUT;
@ -136,7 +137,7 @@ public class EaglerRateLimiter {
}
}
private final Map<String, RateLimiter> ratelimiters = new HashMap();
private final Map<String, RateLimiter> ratelimiters = new HashMap<>();
public RateLimitStatus rateLimit(String addr) {
addr = addr.toLowerCase();
@ -156,7 +157,7 @@ public class EaglerRateLimiter {
}
public void tick() {
long millis = System.currentTimeMillis();
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
synchronized(ratelimiters) {
Iterator<RateLimiter> itr = ratelimiters.values().iterator();
while(itr.hasNext()) {
@ -181,7 +182,7 @@ public class EaglerRateLimiter {
int limitLockout = config.getInt("limit_lockout", -1);
int lockoutDuration = config.getInt("lockout_duration", -1);
Collection<String> exc = (Collection<String>) config.getList("exceptions");
List<String> exceptions = new ArrayList();
List<String> exceptions = new ArrayList<>();
for(String str : exc) {
exceptions.add(str.toLowerCase());
}

View File

@ -0,0 +1,250 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import com.google.common.html.HtmlEscapers;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.repackage.lang3.StrTokenizer;
import net.md_5.bungee.BungeeCord;
/**
* 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 ServerInfoTemplateParser {
private static final Gson jsonEscaper = (new GsonBuilder()).disableHtmlEscaping().create();
private static class State {
private boolean evalAllowed;
private File baseDir;
private Map<String, String> globals;
private boolean htmlEscape;
private boolean strEscape;
private boolean disableMacros;
private boolean enableEval;
private State(File baseDir, boolean evalAllowed, Map<String, String> globals) {
this.baseDir = baseDir;
this.evalAllowed = evalAllowed;
this.globals = globals;
}
private State push() {
return new State(baseDir, evalAllowed, globals);
}
}
public static String loadTemplate(String content, File baseDir, boolean evalAllowed, Map<String, String> globals) throws IOException {
return loadTemplate(content, new State(baseDir, evalAllowed, globals));
}
private static String loadTemplate(String content, State state) throws IOException {
StringBuilder ret = new StringBuilder();
int i = 0, j = 0;
while((i = content.indexOf("{%", j)) != -1) {
ret.append(content, j, i);
j = i;
i = content.indexOf("%}", j + 2);
if(i != -1) {
ret.append(processMacro(content.substring(j + 2, i), state));
j = i + 2;
}else {
break;
}
}
ret.append(content, j, content.length());
return ret.toString();
}
public static class InvalidMacroException extends RuntimeException {
public InvalidMacroException(String message, Throwable cause) {
super(message, cause);
}
public InvalidMacroException(String message) {
super(message);
}
}
private static String processMacro(String content, State state) throws IOException {
String trimmed = content.trim();
try {
String[] strs = (new StrTokenizer(trimmed, ' ', '`')).getTokenArray();
if(strs.length < 1) {
return "{%" + content + "%}";
}
if(strs[0].equals("disablemacros") && strs.length == 2) {
switch(strs[1]) {
case "on":
if(state.disableMacros) {
return "{%" + content + "%}";
}else {
state.disableMacros = true;
return "";
}
case "off":
state.disableMacros = false;
return "";
default:
if(state.disableMacros) {
return "{%" + content + "%}";
}else {
throw new InvalidMacroException("Unknown disablemacros mode: " + strs[1] + " (Expected: on, off)");
}
}
}else if(!state.disableMacros) {
switch(strs[0]) {
case "embed":
argCheck(3, strs.length);
switch(strs[1]) {
case "base64":
return Base64.encodeBase64String(EaglerXBungeeAPIHelper.loadFileToByteArrayServerInfo(new File(state.baseDir, strs[2])));
case "text":
return escapeMacroResult(EaglerXBungeeAPIHelper.loadFileToStringServerInfo(new File(state.baseDir, strs[2])), state);
case "eval":
if(state.evalAllowed) {
return escapeMacroResult(loadTemplate(EaglerXBungeeAPIHelper.loadFileToStringServerInfo(new File(state.baseDir, strs[2])), state.push()), state);
}else {
throw new InvalidMacroException("Template tried to eval file \"" + strs[2] + "\"! (eval is disabled)");
}
default:
throw new InvalidMacroException("Unknown embed mode: " + strs[1] + " (Expected: base64, text, eval)");
}
case "htmlescape":
argCheck(2, strs.length);
switch(strs[1]) {
case "on":
state.htmlEscape = true;
return "";
case "off":
state.htmlEscape = false;
return "";
default:
throw new InvalidMacroException("Unknown htmlescape mode: " + strs[1] + " (Expected: on, off)");
}
case "strescape":
argCheck(2, strs.length);
switch(strs[1]) {
case "on":
state.strEscape = true;
return "";
case "off":
state.strEscape = false;
return "";
default:
throw new InvalidMacroException("Unknown strescape mode: " + strs[1] + " (Expected: on, off)");
}
case "eval":
argCheck(2, strs.length);
switch(strs[1]) {
case "on":
if(!state.evalAllowed) {
throw new InvalidMacroException("Template tried to enable eval! (eval is disabled)");
}
state.enableEval = true;
return "";
case "off":
state.enableEval = false;
return "";
default:
throw new InvalidMacroException("Unknown eval mode: " + strs[1] + " (Expected: on, off)");
}
case "global":
argCheck(2, 3, strs.length);
String ret = state.globals.get(strs[1]);
if(ret == null) {
if(strs.length == 3) {
ret = strs[2];
}else {
throw new InvalidMacroException("Unknown global \"" + strs[1] + "\"! (Available: " + String.join(", ", state.globals.keySet()) + ")");
}
}
return escapeMacroResult(ret, state);
case "property":
argCheck(2, 3, strs.length);
ret = System.getProperty(strs[1]);
if(ret == null) {
if(strs.length == 3) {
ret = strs[2];
}else {
throw new InvalidMacroException("Unknown system property \"" + strs[1] + "\"!");
}
}
return escapeMacroResult(ret, state);
case "text":
argCheck(2, strs.length);
return escapeMacroResult(strs[1], state);
case "translate":
argCheckMin(2, strs.length);
String[] additionalArgs = new String[strs.length - 2];
System.arraycopy(strs, 2, additionalArgs, 0, additionalArgs.length);
return escapeMacroResult(BungeeCord.getInstance().getTranslation(strs[1], (Object[])additionalArgs), state);
default:
return "{%" + content + "%}";
}
}else {
return "{%" + content + "%}";
}
}catch(InvalidMacroException ex) {
throw new IOException("Invalid macro: {% " + trimmed + " %}, message: " + ex.getMessage(), ex);
}catch(Throwable th) {
throw new IOException("Error processing: {% " + trimmed + " %}, raised: " + th.toString(), th);
}
}
private static String escapeMacroResult(String str, State state) throws IOException {
if(str.length() > 0) {
if(state.evalAllowed && state.enableEval) {
str = loadTemplate(str, state.push());
}
if(state.strEscape) {
str = jsonEscaper.toJson(str);
if(str.length() >= 2) {
str = str.substring(1, str.length() - 1);
}
}
if(state.htmlEscape) {
str = HtmlEscapers.htmlEscaper().escape(str);
}
}
return str;
}
private static void argCheck(int expect, int actual) {
if(expect != actual) {
throw new InvalidMacroException("Wrong number of arguments (" + actual + ", expected " + expect + ")");
}
}
private static void argCheck(int expectMin, int expectMax, int actual) {
if(expectMin > actual || expectMax < actual) {
throw new InvalidMacroException("Wrong number of arguments (" + actual + ", expected " + expectMin + " to " + expectMax + ")");
}
}
private static void argCheckMin(int expectMin, int actual) {
if(expectMin > actual) {
throw new InvalidMacroException("Wrong number of arguments (expected " + expectMin + " or more, got " + actual + ")");
}
}
}

View File

@ -1,9 +1,10 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol;
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import net.md_5.bungee.protocol.DefinedPacket;
import java.io.IOException;
import java.io.OutputStream;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
* 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
@ -17,16 +18,14 @@ import net.md_5.bungee.protocol.DefinedPacket;
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglerProtocolAccessProxy {
public static int getPacketId(EaglerBungeeProtocol protocol, int protocolVersion, DefinedPacket pkt, boolean server) {
final EaglerBungeeProtocol.DirectionData prot = server ? protocol.TO_CLIENT : protocol.TO_SERVER;
return prot.getId((Class) pkt.getClass(), protocolVersion);
public class TestOutputStream extends OutputStream {
@Override
public void write(int b) throws IOException {
}
public static DefinedPacket createPacket(EaglerBungeeProtocol protocol, int protocolVersion, int packetId, boolean server) {
final EaglerBungeeProtocol.DirectionData prot = server ? protocol.TO_CLIENT : protocol.TO_SERVER;
return prot.createPacket(packetId, protocolVersion);
@Override
public void write(byte[] b, int o, int l) throws IOException {
}
}

View File

@ -1,6 +1,5 @@
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;
@ -11,20 +10,18 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.EaglerBackendRPCProtocol;
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.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol.BackendRPCSessionHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol.GameProtocolMessageController;
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;
@ -39,7 +36,7 @@ import net.md_5.bungee.event.EventHandler;
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
@ -55,7 +52,6 @@ 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 static final String GET_DOMAIN_CHANNEL = "EAG|GetDomain";
public final EaglerXBungee plugin;
@ -68,50 +64,64 @@ public class EaglerPacketEventListener implements Listener {
public void onPluginMessage(final PluginMessageEvent event) {
if(event.getSender() instanceof UserConnection) {
final UserConnection player = (UserConnection)event.getSender();
String tag = event.getTag();
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(), player, plugin.getSkinService());
} catch (IOException e) {
event.getSender().disconnect(new TextComponent("Skin packet error!"));
EaglerXBungee.logger().log(Level.SEVERE, "Eagler user \"" + player.getName() + "\" raised an exception handling skins!", e);
}
}
});
}else if(CapeServiceOffline.CHANNEL.equals(event.getTag())) {
event.setCancelled(true);
EaglerInitialHandler initialHandler = (EaglerInitialHandler)player.getPendingConnection();
GameProtocolMessageController msgController = initialHandler.getEaglerMessageController();
if(msgController != null) {
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);
if(msgController.handlePacket(tag, event.getData())) {
event.setCancelled(true);
return;
}
} catch (Throwable e) {
event.getSender().disconnect(new TextComponent("Eaglercraft packet error!"));
event.setCancelled(true);
return;
}
}
}
if(tag.equals(EaglerBackendRPCProtocol.CHANNEL_NAME)) {
event.getSender().disconnect(new TextComponent("Nope!"));
event.setCancelled(true);
return;
}
if(tag.equals(EaglerBackendRPCProtocol.CHANNEL_NAME_READY)) {
event.setCancelled(true);
return;
}
}else if(event.getSender() instanceof Server && event.getReceiver() instanceof UserConnection) {
UserConnection player = (UserConnection)event.getReceiver();
if(GET_DOMAIN_CHANNEL.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 });
}else {
((Server)event.getSender()).sendData("EAG|Domain", domain.getBytes(StandardCharsets.UTF_8));
String tag = event.getTag();
if(player.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler initialHandler = (EaglerInitialHandler)player.getPendingConnection();
if(EaglerBackendRPCProtocol.CHANNEL_NAME.equals(tag)) {
event.setCancelled(true);
try {
initialHandler.handleBackendRPCPacket((Server)event.getSender(), event.getData());
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + ((UserConnection) event.getReceiver()).getName()
+ "]: Caught an exception handling backend RPC packet!", t);
}
}else if(GET_DOMAIN_CHANNEL.equals(tag)) {
event.setCancelled(true);
String domain = initialHandler.getOrigin();
if(domain != null) {
((Server)event.getSender()).sendData("EAG|Domain", domain.getBytes(StandardCharsets.UTF_8));
}else {
((Server)event.getSender()).sendData("EAG|Domain", new byte[] { 0 });
}
}
}else {
if(EaglerBackendRPCProtocol.CHANNEL_NAME.equals(tag)) {
event.setCancelled(true);
try {
BackendRPCSessionHandler.handlePacketOnVanilla((Server) event.getSender(),
(UserConnection) event.getReceiver(), event.getData());
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + ((UserConnection) event.getReceiver()).getName()
+ "]: Caught an exception handling backend RPC packet!", t);
}
}
}
}
@ -175,19 +185,7 @@ public class EaglerPacketEventListener implements Listener {
@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);
}
}
EaglerPipeline.addServerConnectListener((UserConnection)event.getPlayer());
}
}
@ -195,10 +193,15 @@ public class EaglerPacketEventListener implements Listener {
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());
if(player.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler handler = (EaglerInitialHandler) player.getPendingConnection();
BackendRPCSessionHandler rpcHandler = handler.getRPCSessionHandler();
if(rpcHandler != null) {
rpcHandler.handleConnectionLost(event.getTarget());
}
if(handler.getEaglerListenerConfig().getEnableVoiceChat()) {
plugin.getVoiceService().handleServerDisconnected(player, event.getTarget());
}
}
}
}

View File

@ -0,0 +1,439 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.repackage.lang3;
import java.util.Arrays;
import org.apache.commons.lang3.ArraySorter;
import org.apache.commons.lang3.StringUtils;
/**
* A matcher class that can be queried to determine if a character array portion
* matches.
* <p>
* This class comes complete with various factory methods. If these do not
* suffice, you can subclass and implement your own matcher.
*
* @since 2.2
* @!deprecated as of 3.6, use commons-text <a href=
* "https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/matcher/StringMatcherFactory.html">
* StringMatcherFactory</a> instead
*/
//@Deprecated
public abstract class StrMatcher {
/**
* Matches the comma character.
*/
private static final StrMatcher COMMA_MATCHER = new CharMatcher(',');
/**
* Matches the tab character.
*/
private static final StrMatcher TAB_MATCHER = new CharMatcher('\t');
/**
* Matches the space character.
*/
private static final StrMatcher SPACE_MATCHER = new CharMatcher(' ');
/**
* Matches the same characters as StringTokenizer, namely space, tab, newline,
* formfeed.
*/
private static final StrMatcher SPLIT_MATCHER = new CharSetMatcher(" \t\n\r\f".toCharArray());
/**
* Matches the String trim() whitespace characters.
*/
private static final StrMatcher TRIM_MATCHER = new TrimMatcher();
/**
* Matches the double quote character.
*/
private static final StrMatcher SINGLE_QUOTE_MATCHER = new CharMatcher('\'');
/**
* Matches the double quote character.
*/
private static final StrMatcher DOUBLE_QUOTE_MATCHER = new CharMatcher('"');
/**
* Matches the single or double quote character.
*/
private static final StrMatcher QUOTE_MATCHER = new CharSetMatcher("'\"".toCharArray());
/**
* Matches no characters.
*/
private static final StrMatcher NONE_MATCHER = new NoMatcher();
// -----------------------------------------------------------------------
/**
* Returns a matcher which matches the comma character.
*
* @return a matcher for a comma
*/
public static StrMatcher commaMatcher() {
return COMMA_MATCHER;
}
/**
* Returns a matcher which matches the tab character.
*
* @return a matcher for a tab
*/
public static StrMatcher tabMatcher() {
return TAB_MATCHER;
}
/**
* Returns a matcher which matches the space character.
*
* @return a matcher for a space
*/
public static StrMatcher spaceMatcher() {
return SPACE_MATCHER;
}
/**
* Matches the same characters as StringTokenizer, namely space, tab, newline
* and formfeed.
*
* @return the split matcher
*/
public static StrMatcher splitMatcher() {
return SPLIT_MATCHER;
}
/**
* Matches the String trim() whitespace characters.
*
* @return the trim matcher
*/
public static StrMatcher trimMatcher() {
return TRIM_MATCHER;
}
/**
* Returns a matcher which matches the single quote character.
*
* @return a matcher for a single quote
*/
public static StrMatcher singleQuoteMatcher() {
return SINGLE_QUOTE_MATCHER;
}
/**
* Returns a matcher which matches the double quote character.
*
* @return a matcher for a double quote
*/
public static StrMatcher doubleQuoteMatcher() {
return DOUBLE_QUOTE_MATCHER;
}
/**
* Returns a matcher which matches the single or double quote character.
*
* @return a matcher for a single or double quote
*/
public static StrMatcher quoteMatcher() {
return QUOTE_MATCHER;
}
/**
* Matches no characters.
*
* @return a matcher that matches nothing
*/
public static StrMatcher noneMatcher() {
return NONE_MATCHER;
}
/**
* Constructor that creates a matcher from a character.
*
* @param ch the character to match, must not be null
* @return a new Matcher for the given char
*/
public static StrMatcher charMatcher(final char ch) {
return new CharMatcher(ch);
}
/**
* Constructor that creates a matcher from a set of characters.
*
* @param chars the characters to match, null or empty matches nothing
* @return a new matcher for the given char[]
*/
public static StrMatcher charSetMatcher(final char... chars) {
if (chars == null || chars.length == 0) {
return NONE_MATCHER;
}
if (chars.length == 1) {
return new CharMatcher(chars[0]);
}
return new CharSetMatcher(chars);
}
/**
* Constructor that creates a matcher from a string representing a set of
* characters.
*
* @param chars the characters to match, null or empty matches nothing
* @return a new Matcher for the given characters
*/
public static StrMatcher charSetMatcher(final String chars) {
if (StringUtils.isEmpty(chars)) {
return NONE_MATCHER;
}
if (chars.length() == 1) {
return new CharMatcher(chars.charAt(0));
}
return new CharSetMatcher(chars.toCharArray());
}
/**
* Constructor that creates a matcher from a string.
*
* @param str the string to match, null or empty matches nothing
* @return a new Matcher for the given String
*/
public static StrMatcher stringMatcher(final String str) {
if (StringUtils.isEmpty(str)) {
return NONE_MATCHER;
}
return new StringMatcher(str);
}
// -----------------------------------------------------------------------
/**
* Constructor.
*/
protected StrMatcher() {
}
/**
* Returns the number of matching characters, zero for no match.
* <p>
* This method is called to check for a match. The parameter {@code pos}
* represents the current position to be checked in the string {@code buffer} (a
* character array which must not be changed). The API guarantees that
* {@code pos} is a valid index for {@code buffer}.
* <p>
* The character array may be larger than the active area to be matched. Only
* values in the buffer between the specified indices may be accessed.
* <p>
* The matching code may check one character or many. It may check characters
* preceding {@code pos} as well as those after, so long as no checks exceed the
* bounds specified.
* <p>
* It must return zero for no match, or a positive number if a match was found.
* The number indicates the number of characters that matched.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index (exclusive) of the active buffer, valid for
* buffer
* @return the number of matching characters, zero for no match
*/
public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd);
/**
* Returns the number of matching characters, zero for no match.
* <p>
* This method is called to check for a match. The parameter {@code pos}
* represents the current position to be checked in the string {@code buffer} (a
* character array which must not be changed). The API guarantees that
* {@code pos} is a valid index for {@code buffer}.
* <p>
* The matching code may check one character or many. It may check characters
* preceding {@code pos} as well as those after.
* <p>
* It must return zero for no match, or a positive number if a match was found.
* The number indicates the number of characters that matched.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @return the number of matching characters, zero for no match
* @since 2.4
*/
public int isMatch(final char[] buffer, final int pos) {
return isMatch(buffer, pos, 0, buffer.length);
}
// -----------------------------------------------------------------------
/**
* Class used to define a set of characters for matching purposes.
*/
static final class CharSetMatcher extends StrMatcher {
/** The set of characters to match. */
private final char[] chars;
/**
* Constructor that creates a matcher from a character array.
*
* @param chars the characters to match, must not be null
*/
CharSetMatcher(final char[] chars) {
this.chars = ArraySorter.sort(chars.clone());
}
/**
* Returns whether or not the given character matches.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
* @return the number of matching characters, zero for no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
return Arrays.binarySearch(chars, buffer[pos]) >= 0 ? 1 : 0;
}
}
// -----------------------------------------------------------------------
/**
* Class used to define a character for matching purposes.
*/
static final class CharMatcher extends StrMatcher {
/** The character to match. */
private final char ch;
/**
* Constructor that creates a matcher that matches a single character.
*
* @param ch the character to match
*/
CharMatcher(final char ch) {
this.ch = ch;
}
/**
* Returns whether or not the given character matches.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
* @return the number of matching characters, zero for no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
return ch == buffer[pos] ? 1 : 0;
}
}
// -----------------------------------------------------------------------
/**
* Class used to define a set of characters for matching purposes.
*/
static final class StringMatcher extends StrMatcher {
/** The string to match, as a character array. */
private final char[] chars;
/**
* Constructor that creates a matcher from a String.
*
* @param str the string to match, must not be null
*/
StringMatcher(final String str) {
chars = str.toCharArray();
}
/**
* Returns whether or not the given text matches the stored string.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
* @return the number of matching characters, zero for no match
*/
@Override
public int isMatch(final char[] buffer, int pos, final int bufferStart, final int bufferEnd) {
final int len = chars.length;
if (pos + len > bufferEnd) {
return 0;
}
for (int i = 0; i < chars.length; i++, pos++) {
if (chars[i] != buffer[pos]) {
return 0;
}
}
return len;
}
@Override
public String toString() {
return super.toString() + ' ' + Arrays.toString(chars);
}
}
// -----------------------------------------------------------------------
/**
* Class used to match no characters.
*/
static final class NoMatcher extends StrMatcher {
/**
* Constructs a new instance of {@code NoMatcher}.
*/
NoMatcher() {
}
/**
* Always returns {@code false}.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
* @return the number of matching characters, zero for no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
return 0;
}
}
// -----------------------------------------------------------------------
/**
* Class used to match whitespace as per trim().
*/
static final class TrimMatcher extends StrMatcher {
/**
* Constructs a new instance of {@code TrimMatcher}.
*/
TrimMatcher() {
}
/**
* Returns whether or not the given character matches.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
* @return the number of matching characters, zero for no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
return buffer[pos] <= 32 ? 1 : 0;
}
}
}

View File

@ -1,7 +1,6 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import io.netty.channel.ChannelHandlerContext;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerBungeeProtocol;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.Protocol;
@ -26,11 +25,11 @@ public class EaglerChannelWrapper extends ChannelWrapper {
super(ctx);
}
public void setProtocol(EaglerBungeeProtocol protocol) {
public void setProtocol(Protocol protocol) {
getHandle().pipeline().get(EaglerMinecraftEncoder.class).setProtocol(protocol);
getHandle().pipeline().get(EaglerMinecraftDecoder.class).setProtocol(protocol);
}
public void setVersion(int protocol) {
getHandle().pipeline().get(EaglerMinecraftEncoder.class).setProtocolVersion(protocol);
getHandle().pipeline().get(EaglerMinecraftDecoder.class).setProtocolVersion(protocol);
@ -41,24 +40,12 @@ public class EaglerChannelWrapper extends ChannelWrapper {
public Protocol getEncodeProtocol() {
EaglerMinecraftEncoder enc;
if (this.getHandle() == null || (enc = this.getHandle().pipeline().get(EaglerMinecraftEncoder.class)) == null) return lastProtocol;
EaglerBungeeProtocol eaglerProtocol = enc.getProtocol();
switch(eaglerProtocol) {
case GAME:
return (lastProtocol = Protocol.GAME);
case HANDSHAKE:
return (lastProtocol = Protocol.HANDSHAKE);
case LOGIN:
return (lastProtocol = Protocol.LOGIN);
case STATUS:
return (lastProtocol = Protocol.STATUS);
default:
return lastProtocol;
}
return (lastProtocol = enc.getProtocol());
}
public void close(Object o) {
super.close(o);
EaglerPipeline.closeChannel(getHandle());
}
}

View File

@ -1,6 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import io.netty.channel.Channel;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.md_5.bungee.UserConnection;
/**
@ -33,11 +34,12 @@ public class EaglerConnectionInstance {
public boolean isRegularHttp = false;
public UserConnection userConnection = null;
public HttpServerQueryHandler queryHandler = null;
public EaglerConnectionInstance(Channel channel) {
this.channel = channel;
this.creationTime = this.lastServerPingPacket = this.lastClientPingPacket =
this.lastClientPongPacket = System.currentTimeMillis();
this.lastClientPongPacket = EaglerXBungeeAPIHelper.steadyTimeMillis();
}
}

View File

@ -1,24 +1,56 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.collect.Collections2;
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.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumWebViewState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.NotificationBadgeBuilder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftVoiceStatusChangeEvent;
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.server.backend_rpc_protocol.BackendRPCSessionHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol.EnumSubscribedEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol.GameProtocolMessageController;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SimpleRateLimiter;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketCustomizePauseMenuV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeHideV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeShowV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifIconsRegisterV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifIconsReleaseV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketRedirectClientV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketSetServerCookieV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketWebViewMessageV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.PacketImageData;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Property;
@ -33,7 +65,7 @@ import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.StatusRequest;
/**
* 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
@ -58,47 +90,84 @@ public class EaglerInitialHandler extends InitialHandler {
}
}
private final int gameProtocolVersion;
private final String username;
private final UUID playerUUID;
private final UUID playerUUIDOffline;
private final UUID playerUUIDRewrite;
private LoginResult loginResult;
private final InetSocketAddress eaglerAddress;
private final InetSocketAddress virtualHost;
private final Unsafe eaglerUnsafe;
protected final int clientProtocolVersion;
protected final int gameProtocolVersion;
protected final String clientBrandString;
protected final String clientVersionString;
protected final UUID clientBrandUUID;
protected final String username;
protected final UUID playerUUID;
protected final UUID playerUUIDOffline;
protected final UUID playerUUIDRewrite;
protected final InetSocketAddress eaglerAddress;
protected final InetSocketAddress virtualHost;
protected final Unsafe eaglerUnsafe;
public final SimpleRateLimiter skinLookupRateLimiter;
public final SimpleRateLimiter skinUUIDLookupRateLimiter;
public final SimpleRateLimiter skinTextureDownloadRateLimiter;
public final SimpleRateLimiter capeLookupRateLimiter;
public final SimpleRateLimiter voiceConnectRateLimiter;
public final String origin;
protected final String origin;
protected final String userAgent;
public final ClientCertificateHolder clientCertificate;
public final Set<ClientCertificateHolder> certificatesToSend;
public final TIntSet certificatesSent;
public boolean currentFNAWSkinEnableStatus = true;
public final EaglerChannelWrapper ch;
public final AtomicBoolean currentFNAWSkinEnableStatus = new AtomicBoolean(true);
public final AtomicBoolean currentFNAWSkinForceStatus = new AtomicBoolean(false);
volatile GameProtocolMessageController messageProtocolController = null;
protected final boolean allowCookie;
protected volatile byte[] cookie;
public volatile SkinPacketVersionCache originalSkin = null;
public volatile GameMessagePacket originalCape = null;
protected final Map<String,byte[]> otherProfileDataFromHanshake;
public boolean isWebViewChannelAllowed = false;
public final AtomicBoolean webViewMessageChannelOpen = new AtomicBoolean(false);
public volatile String webViewMessageChannelName = null;
public final AtomicBoolean hasSentServerInfo = new AtomicBoolean(false);
public final List<GameMessagePacket> serverInfoSendBuffer = new LinkedList<>();
protected BackendRPCSessionHandler backedRPCSessionHandler = null;
protected final AtomicReference<EaglercraftVoiceStatusChangeEvent.EnumVoiceState> lastVoiceState = new AtomicReference<>(
EaglercraftVoiceStatusChangeEvent.EnumVoiceState.SERVER_DISABLE);
private static final Property[] NO_PROPERTIES = new Property[0];
public EaglerInitialHandler(BungeeCord bungee, EaglerListenerConfig listener, final ChannelWrapper ch,
int gameProtocolVersion, String username, UUID playerUUID, UUID offlineUUID, InetSocketAddress address,
String host, String origin, ClientCertificateHolder clientCertificate) {
public EaglerInitialHandler(BungeeCord bungee, EaglerListenerConfig listener, final EaglerChannelWrapper ch,
int clientProtocolVersion, int gameProtocolVersion, String clientBrandString, String clientVersionString,
UUID clientBrandUUID, String username, UUID playerUUID, UUID offlineUUID, InetSocketAddress address,
String host, String origin, String userAgent, ClientCertificateHolder clientCertificate,
boolean allowCookie, byte[] cookie, Map<String,byte[]> otherProfileData) {
super(bungee, listener);
this.ch = ch;
this.clientProtocolVersion = clientProtocolVersion;
this.gameProtocolVersion = gameProtocolVersion;
this.clientBrandString = clientBrandString;
this.clientVersionString = clientVersionString;
this.clientBrandUUID = clientBrandUUID;
this.username = username;
this.playerUUID = playerUUID;
this.playerUUIDOffline = offlineUUID;
this.playerUUIDRewrite = bungee.config.isIpForward() ? playerUUID : offlineUUID;
this.eaglerAddress = address;
this.origin = origin;
this.userAgent = userAgent;
this.skinLookupRateLimiter = new SimpleRateLimiter();
this.skinUUIDLookupRateLimiter = new SimpleRateLimiter();
this.skinTextureDownloadRateLimiter = new SimpleRateLimiter();
this.capeLookupRateLimiter = new SimpleRateLimiter();
this.voiceConnectRateLimiter = new SimpleRateLimiter();
this.allowCookie = allowCookie;
this.cookie = cookie;
this.otherProfileDataFromHanshake = otherProfileData;
this.clientCertificate = clientCertificate;
this.certificatesToSend = new HashSet();
this.certificatesToSend = new HashSet<>();
this.certificatesSent = new TIntHashSet();
EaglerBungeeConfig conf = EaglerXBungee.getEagler().getConfig();
SPacketCustomizePauseMenuV4EAG pkt = conf.getPauseMenuConf().getPacket();
this.isWebViewChannelAllowed = pkt != null
&& (pkt.serverInfoEmbedPerms & SPacketCustomizePauseMenuV4EAG.SERVER_INFO_EMBED_PERMS_MESSAGE_API) != 0;
this.backedRPCSessionHandler = conf.getEnableBackendRPCAPI()
? BackendRPCSessionHandler.createForPlayer(this) : null;
if(clientCertificate != null) {
this.certificatesSent.add(clientCertificate.hashCode());
}
@ -131,6 +200,255 @@ public class EaglerInitialHandler extends InitialHandler {
}
}
public GameProtocolMessageController getEaglerMessageController() {
return messageProtocolController;
}
public GamePluginMessageProtocol getEaglerProtocol() {
return messageProtocolController == null ? GamePluginMessageProtocol.getByVersion(clientProtocolVersion)
: messageProtocolController.protocol;
}
public int getEaglerProtocolHandshake() {
return clientProtocolVersion;
}
public void sendEaglerMessage(GameMessagePacket pkt) {
if(messageProtocolController != null) {
try {
messageProtocolController.sendPacket(pkt);
} catch (IOException e) {
this.disconnect(new TextComponent("Failed to write eaglercraft packet! (" + e.toString() + ")"));
}
}else {
throw new IllegalStateException("Race condition detected, messageProtocolController is null! (wait until getEaglerMessageController() does not return null before sending the packet)");
}
}
public boolean getWebViewSupport() {
return getEaglerProtocol().ver >= 4;
}
public void setWebViewChannelAllowed(boolean en) {
isWebViewChannelAllowed = en;
}
public boolean getWebViewChannelAllowed() {
return isWebViewChannelAllowed;
}
public boolean getWebViewMessageChannelOpen() {
return webViewMessageChannelOpen.get();
}
public String getWebViewMessageChannelName() {
return webViewMessageChannelName;
}
public void sendWebViewMessage(int type, byte[] bytes) {
if(webViewMessageChannelOpen.get()) {
sendEaglerMessage(new SPacketWebViewMessageV4EAG(type, bytes));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to send a webview message to player \"" + username + "\", but the player doesn't have a webview message channel open!");
}
}
public void sendWebViewMessage(String str) {
if(webViewMessageChannelOpen.get()) {
sendEaglerMessage(new SPacketWebViewMessageV4EAG(str));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to send a webview message to player \"" + username + "\", but the player doesn't have a webview message channel open!");
}
}
public void sendWebViewMessage(byte[] bin) {
if(webViewMessageChannelOpen.get()) {
sendEaglerMessage(new SPacketWebViewMessageV4EAG(bin));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to send a webview message to player \"" + username + "\", but the player doesn't have a webview message channel open!");
}
}
public EnumWebViewState getWebViewState() {
if(!getWebViewSupport()) {
return EnumWebViewState.NOT_SUPPORTED;
}
if(isWebViewChannelAllowed) {
if(webViewMessageChannelOpen.get()) {
return EnumWebViewState.CHANNEL_OPEN;
}else {
return EnumWebViewState.CHANNEL_CLOSED;
}
}else {
return EnumWebViewState.SERVER_DISABLE;
}
}
public boolean getCookieAllowed() {
return allowCookie;
}
public byte[] getCookieData() {
return allowCookie ? cookie : null;
}
public void setCookieData(byte[] data, long expiresAfter, TimeUnit timeUnit) {
setCookieData(data, timeUnit.toSeconds(expiresAfter), false, true);
}
public void setCookieData(byte[] data, long expiresAfter, TimeUnit timeUnit, boolean revokeQuerySupported) {
setCookieData(data, timeUnit.toSeconds(expiresAfter), revokeQuerySupported, true);
}
public void setCookieData(byte[] data, long expiresAfter, TimeUnit timeUnit, boolean revokeQuerySupported, boolean clientSaveCookieToDisk) {
setCookieData(data, timeUnit.toSeconds(expiresAfter), revokeQuerySupported, clientSaveCookieToDisk);
}
public void setCookieData(byte[] data, long expiresAfterSec, boolean revokeQuerySupported, boolean clientSaveCookieToDisk) {
if(allowCookie) {
if(expiresAfterSec < 0l) {
expiresAfterSec = 0l;
data = null;
}
if(data == null) {
cookie = null;
sendEaglerMessage(new SPacketSetServerCookieV4EAG(null, 01, false, false));
return;
}
if(data.length > 255) {
throw new IllegalArgumentException("Cookie cannot be longer than 255 bytes!");
}
if(expiresAfterSec > 604800l) {
throw new IllegalArgumentException("Cookie cannot be set for longer than 7 days! (tried " + (expiresAfterSec / 604800l) + " days)");
}
cookie = data;
sendEaglerMessage(new SPacketSetServerCookieV4EAG(data, expiresAfterSec, revokeQuerySupported, clientSaveCookieToDisk));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to set a cookie for player \"" + username + "\", but the player has cookies disabled!");
}
}
public void clearCookieData() {
setCookieData(null, 0, false, false);
}
public boolean notificationSupported() {
return clientProtocolVersion >= 4;
}
public void registerNotificationIcon(UUID uuid, PacketImageData imageData) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifIconsRegisterV4EAG(
Arrays.asList(new SPacketNotifIconsRegisterV4EAG.CreateIcon(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits(), imageData))));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to register notification icons for player \"" + username + "\", but the player has notifications disabled!");
}
}
public void registerNotificationIcons(Map<UUID,PacketImageData> imageDatas) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifIconsRegisterV4EAG(
new ArrayList<>(Collections2.transform(imageDatas.entrySet(), (etr) -> {
UUID key = etr.getKey();
return new SPacketNotifIconsRegisterV4EAG.CreateIcon(key.getMostSignificantBits(),
key.getLeastSignificantBits(), etr.getValue());
}))));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to register notification icons for player \"" + username + "\", but the player has notifications disabled!");
}
}
public void showNotificationBadge(NotificationBadgeBuilder badgeBuilder) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(badgeBuilder.buildPacket());
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to show notification badges to player \"" + username + "\", but the player has notifications disabled!");
}
}
public void showNotificationBadge(SPacketNotifBadgeShowV4EAG badgePacket) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(badgePacket);
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to show notification badges to player \"" + username + "\", but the player has notifications disabled!");
}
}
public void hideNotificationBadge(UUID badgeUUID) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifBadgeHideV4EAG(badgeUUID.getMostSignificantBits(), badgeUUID.getLeastSignificantBits()));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to hide notification badges for player \"" + username + "\", but the player has notifications disabled!");
}
}
public void releaseNotificationIcon(UUID uuid) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifIconsReleaseV4EAG(
Arrays.asList(new SPacketNotifIconsReleaseV4EAG.DestroyIcon(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits()))));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to release notification icons for player \"" + username + "\", but the player has notifications disabled!");
}
}
public void releaseNotificationIcons(Collection<UUID> uuids) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifIconsReleaseV4EAG(new ArrayList<>(Collections2.transform(uuids,
(etr) -> new SPacketNotifIconsReleaseV4EAG.DestroyIcon(etr.getMostSignificantBits(),
etr.getLeastSignificantBits())))));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to release notification icons for player \"" + username + "\", but the player has notifications disabled!");
}
}
public boolean redirectToWebSocketSupported() {
return clientProtocolVersion >= 4;
}
public void redirectPlayerToWebSocket(String serverAddress) {
if(getEaglerProtocol().ver >= 4) {
sendEaglerMessage(new SPacketRedirectClientV4EAG(serverAddress));
}else {
EaglerXBungee.logger().warning("[" + getSocketAddress().toString() + "]: Some plugin tried to redirect player \"" + username + "\" to a different websocket, but that player's client doesn't support this feature!");
}
}
public BackendRPCSessionHandler getRPCSessionHandler() {
return backedRPCSessionHandler;
}
public boolean getRPCEventSubscribed(EnumSubscribedEvent event) {
return backedRPCSessionHandler != null && backedRPCSessionHandler.isSubscribed(event);
}
public void handleBackendRPCPacket(Server server, byte[] data) {
if(backedRPCSessionHandler != null) {
backedRPCSessionHandler.handleRPCPacket(server, data);
}else {
EaglerXBungee.logger().severe("[" + getSocketAddress().toString() + "]: Server tried to send backend RPC packet to player \"" + username + "\" but this feature is not enabled. Enable it by setting \"enable_backend_rpc_api: true\" in settings.yml");
}
}
public void fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState state) {
EaglercraftVoiceStatusChangeEvent.EnumVoiceState oldState = lastVoiceState.getAndSet(state);
if(state != oldState) {
BungeeCord.getInstance().getPluginManager().callEvent(new EaglercraftVoiceStatusChangeEvent(
EaglerXBungeeAPIHelper.getPlayer(this), getEaglerListenerConfig(), this, oldState, state));
}
}
public String getEaglerBrandString() {
return clientBrandString;
}
public String getEaglerVersionString() {
return clientVersionString;
}
public UUID getClientBrandUUID() {
return clientBrandUUID;
}
public static UUID generateOfflineUUID(byte[] username) {
String offlinePlayerStr = "OfflinePlayer:";
byte[] uuidHashGenerator = new byte[offlinePlayerStr.length() + username.length];
@ -139,16 +457,29 @@ public class EaglerInitialHandler extends InitialHandler {
return UUID.nameUUIDFromBytes(uuidHashGenerator);
}
void setLoginProfile(LoginResult obj) {
this.loginResult = obj;
private static final Field loginProfileField;
static {
try {
Field f = InitialHandler.class.getDeclaredField("loginProfile");
f.setAccessible(true);
f.set(this, obj);
loginProfileField = InitialHandler.class.getDeclaredField("loginProfile");
loginProfileField.setAccessible(true);
}catch(Throwable t) {
throw new RuntimeException("Could not access loginProfile field", t);
}
}
void setLoginProfile(LoginResult obj) {
try {
loginProfileField.set(this, obj);
}catch(Throwable t) {
throw new RuntimeException("Could not perform reflection", t);
}
}
public byte[] getOtherProfileDataFromHandshake(String name) {
return otherProfileDataFromHanshake.get(name);
}
@Override
public void handle(PacketWrapper packet) throws Exception {
}
@ -266,11 +597,6 @@ public class EaglerInitialHandler extends InitialHandler {
return playerUUID.toString().replace("-", "");
}
@Override
public LoginResult getLoginProfile() {
return loginResult;
}
@Override
public InetSocketAddress getVirtualHost() {
return virtualHost;
@ -290,7 +616,12 @@ public class EaglerInitialHandler extends InitialHandler {
return origin;
}
public String getUserAgent() {
return userAgent;
}
public EaglerListenerConfig getEaglerListenerConfig() {
return (EaglerListenerConfig)getListener();
}
}

View File

@ -3,7 +3,6 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;

View File

@ -11,10 +11,8 @@ import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.util.ReferenceCountUtil;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerBungeeProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerProtocolAccessProxy;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Protocol;
@ -36,7 +34,7 @@ import net.md_5.bungee.protocol.ProtocolConstants.Direction;
*
*/
public class EaglerMinecraftDecoder extends MessageToMessageDecoder<WebSocketFrame> {
private EaglerBungeeProtocol protocol;
private Protocol protocol;
private final boolean server;
private int protocolVersion;
private static Constructor<PacketWrapper> packetWrapperConstructor = null;
@ -47,27 +45,13 @@ public class EaglerMinecraftDecoder extends MessageToMessageDecoder<WebSocketFra
return;
}
EaglerConnectionInstance con = ctx.channel().attr(EaglerPipeline.CONNECTION_INSTANCE).get();
long millis = System.currentTimeMillis();
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
if(frame instanceof BinaryWebSocketFrame) {
BinaryWebSocketFrame in = (BinaryWebSocketFrame) frame;
ByteBuf buf = in.content();
buf.markReaderIndex();
int pktId = DefinedPacket.readVarInt(buf);
DefinedPacket pkt = EaglerProtocolAccessProxy.createPacket(protocol, protocolVersion, pktId, server);
Protocol bungeeProtocol = null;
switch(this.protocol) {
case GAME:
bungeeProtocol = Protocol.GAME;
break;
case HANDSHAKE:
bungeeProtocol = Protocol.HANDSHAKE;
break;
case LOGIN:
bungeeProtocol = Protocol.LOGIN;
break;
case STATUS:
bungeeProtocol = Protocol.STATUS;
}
if(pkt != null) {
pkt.read(buf, server ? Direction.TO_CLIENT : Direction.TO_SERVER, protocolVersion);
if(buf.isReadable()) {
@ -75,11 +59,11 @@ public class EaglerMinecraftDecoder extends MessageToMessageDecoder<WebSocketFra
pkt.getClass().getSimpleName() + " had extra bytes! (" + buf.readableBytes() + ")");
}else {
buf.resetReaderIndex();
out.add(this.wrapPacket(pkt, buf, bungeeProtocol));
out.add(this.wrapPacket(pkt, buf, protocol));
}
}else {
buf.resetReaderIndex();
out.add(this.wrapPacket(null, buf, bungeeProtocol));
out.add(this.wrapPacket(null, buf, protocol));
}
}else if(frame instanceof PingWebSocketFrame) {
if(millis - con.lastClientPingPacket > 500l) {
@ -93,17 +77,17 @@ public class EaglerMinecraftDecoder extends MessageToMessageDecoder<WebSocketFra
}
}
public EaglerMinecraftDecoder(final EaglerBungeeProtocol protocol, final boolean server, final int protocolVersion) {
public EaglerMinecraftDecoder(Protocol protocol, boolean server, int protocolVersion) {
this.protocol = protocol;
this.server = server;
this.protocolVersion = protocolVersion;
}
public void setProtocol(final EaglerBungeeProtocol protocol) {
public void setProtocol(Protocol protocol) {
this.protocol = protocol;
}
public void setProtocolVersion(final int protocolVersion) {
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}

View File

@ -7,8 +7,6 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerBungeeProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerProtocolAccessProxy;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.ProtocolConstants.Direction;
@ -30,32 +28,18 @@ import net.md_5.bungee.protocol.ProtocolConstants.Direction;
*/
public class EaglerMinecraftEncoder extends MessageToMessageEncoder<DefinedPacket> {
private EaglerBungeeProtocol protocol;
private Protocol protocol;
private boolean server;
private int protocolVersion;
private static Method meth = null;
@Override
protected void encode(ChannelHandlerContext ctx, DefinedPacket msg, List<Object> out) throws Exception {
Protocol bungeeProtocol = null;
switch(this.protocol) {
case GAME:
bungeeProtocol = Protocol.GAME;
break;
case HANDSHAKE:
bungeeProtocol = Protocol.HANDSHAKE;
break;
case LOGIN:
bungeeProtocol = Protocol.LOGIN;
break;
case STATUS:
bungeeProtocol = Protocol.STATUS;
}
ByteBuf buf = ctx.alloc().buffer();
int pk = EaglerProtocolAccessProxy.getPacketId(protocol, protocolVersion, msg, server);
DefinedPacket.writeVarInt(pk, buf);
try {
msg.write(buf, bungeeProtocol, server ? Direction.TO_CLIENT : Direction.TO_SERVER, protocolVersion);
msg.write(buf, protocol, server ? Direction.TO_CLIENT : Direction.TO_SERVER, protocolVersion);
} catch (NoSuchMethodError e) {
try {
if (meth == null) {
@ -73,21 +57,21 @@ public class EaglerMinecraftEncoder extends MessageToMessageEncoder<DefinedPacke
out.add(new BinaryWebSocketFrame(buf));
}
public EaglerMinecraftEncoder(final EaglerBungeeProtocol protocol, final boolean server, final int protocolVersion) {
public EaglerMinecraftEncoder(Protocol protocol, boolean server, int protocolVersion) {
this.protocol = protocol;
this.server = server;
this.protocolVersion = protocolVersion;
}
public void setProtocol(final EaglerBungeeProtocol protocol) {
public void setProtocol(Protocol protocol) {
this.protocol = protocol;
}
public void setProtocolVersion(final int protocolVersion) {
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public EaglerBungeeProtocol getProtocol() {
public Protocol getProtocol() {
return this.protocol;
}

View File

@ -4,12 +4,18 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.ArrayUtils;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
@ -25,11 +31,21 @@ import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensio
import io.netty.handler.codec.http.websocketx.extensions.compression.DeflateFrameServerExtensionHandshaker;
import io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker;
import io.netty.util.AttributeKey;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.EaglerBackendRPCProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
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.server.EaglerInitialHandler.ClientCertificateHolder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketEnableFNAWSkinsEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketUpdateCertEAG;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.scheduler.BungeeScheduler;
/**
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
@ -54,11 +70,13 @@ public class EaglerPipeline {
public static final AttributeKey<InetAddress> REAL_ADDRESS = AttributeKey.valueOf("RealAddress");
public static final AttributeKey<String> HOST = AttributeKey.valueOf("Host");
public static final AttributeKey<String> ORIGIN = AttributeKey.valueOf("Origin");
public static final AttributeKey<String> USER_AGENT = AttributeKey.valueOf("UserAgent");
public static final int LOW_MARK = Integer.getInteger("net.md_5.bungee.low_mark", 524288);
public static final int HIGH_MARK = Integer.getInteger("net.md_5.bungee.high_mark", 2097152);
public static final WriteBufferWaterMark MARK = new WriteBufferWaterMark(LOW_MARK, HIGH_MARK);
public static final Collection<Channel> openChannels = new LinkedList();
public static final Collection<Channel> openChannels = new LinkedList<>();
public static final Set<UserConnection> waitingServerConnections = new HashSet<>();
public static final String UPDATE_CERT_CHANNEL = "EAG|UpdateCert-1.8";
@ -74,7 +92,7 @@ public class EaglerPipeline {
long httpTimeout = conf.getBuiltinHttpServerTimeout();
List<Channel> channelsList;
synchronized(openChannels) {
long millis = System.currentTimeMillis();
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
Iterator<Channel> channelIterator = openChannels.iterator();
while(channelIterator.hasNext()) {
Channel c = channelIterator.next();
@ -82,7 +100,15 @@ public class EaglerPipeline {
long handshakeTimeoutForConnection = 500l;
if(i.isRegularHttp) handshakeTimeoutForConnection = httpTimeout;
else if(i.isWebSocket) handshakeTimeoutForConnection = handshakeTimeout;
if(i == null || (!i.hasBeenForwarded && millis - i.creationTime > handshakeTimeoutForConnection)
boolean hasTimeout = !i.hasBeenForwarded;
if(i.queryHandler != null) {
long l = i.queryHandler.getMaxAge();
hasTimeout = l != -1l;
if(hasTimeout) {
handshakeTimeoutForConnection = l;
}
}
if((hasTimeout && millis - i.creationTime > handshakeTimeoutForConnection)
|| millis - i.lastClientPongPacket > keepAliveTimeout || !c.isActive()) {
if(c.isActive()) {
c.close();
@ -102,7 +128,105 @@ public class EaglerPipeline {
}
}
}
channelsList = new ArrayList(openChannels);
channelsList = new ArrayList<>(openChannels);
}
List<UserConnection> readyServerConnections = null;
synchronized(waitingServerConnections) {
Iterator<UserConnection> connIterator = waitingServerConnections.iterator();
while(connIterator.hasNext()) {
UserConnection userCon = connIterator.next();
if(userCon.isConnected()) {
ServerConnection serverCon = userCon.getServer();
if(serverCon != null) {
if(readyServerConnections == null) {
readyServerConnections = new ArrayList<>(4);
}
readyServerConnections.add(userCon);
connIterator.remove();
}
}else {
connIterator.remove();
}
}
}
if(readyServerConnections != null) {
for(int i = 0, l = readyServerConnections.size(); i < l; ++i) {
handleServerConnectionReady(readyServerConnections.get(i));
}
}
boolean updateLoop = !conf.getUpdateConfig().isBlockAllClientUpdates();
final AtomicInteger sizeTracker = updateLoop ? new AtomicInteger(0) : null;
final int rateLimitParam = conf.getUpdateConfig().getCertPacketDataRateLimit() / 4;
final int serverInfoSendRate = Math.max(conf.getPauseMenuConf().getInfoSendRate(), 1);
BungeeScheduler sched = BungeeCord.getInstance().getScheduler();
for(Channel c : channelsList) {
EaglerConnectionInstance conn = c.attr(EaglerPipeline.CONNECTION_INSTANCE).get();
if(conn.userConnection == null) {
continue;
}
final EaglerInitialHandler i = (EaglerInitialHandler)conn.userConnection.getPendingConnection();
boolean certToSend = false;
if(updateLoop) {
synchronized(i.certificatesToSend) {
if(!i.certificatesToSend.isEmpty()) {
certToSend = true;
}
}
}
boolean serverInfoToSend = false;
synchronized(i.serverInfoSendBuffer) {
if(!i.serverInfoSendBuffer.isEmpty()) {
serverInfoToSend = true;
}
}
if(certToSend || serverInfoToSend) {
final boolean do_certToSend = certToSend;
final boolean do_serverInfoToSend = serverInfoToSend;
sched.runAsync(EaglerXBungee.getEagler(), () -> {
if(do_certToSend) {
ClientCertificateHolder certHolder = null;
synchronized(i.certificatesToSend) {
if(i.certificatesToSend.size() > 0) {
Iterator<ClientCertificateHolder> itr = i.certificatesToSend.iterator();
certHolder = itr.next();
itr.remove();
}
}
if(certHolder != null && sizeTracker.getAndAdd(certHolder.data.length) < rateLimitParam) {
int identityHash = certHolder.hashCode();
boolean bb;
synchronized(i.certificatesSent) {
bb = i.certificatesSent.add(identityHash);
}
if(bb) {
i.sendEaglerMessage(new SPacketUpdateCertEAG(certHolder.data));
}
}
}
if(do_serverInfoToSend) {
List<GameMessagePacket> toSend = i.serverInfoSendBuffer;
synchronized(toSend) {
if(!toSend.isEmpty()) {
try {
if(serverInfoSendRate == 1) {
i.getEaglerMessageController().sendPacketImmediately(toSend.remove(0));
}else {
for(int j = 0; j < serverInfoSendRate; ++j) {
if(!toSend.isEmpty()) {
i.getEaglerMessageController().sendPacketImmediately(toSend.remove(0));
}else {
break;
}
}
}
}catch(Throwable t) {
log.log(Level.SEVERE, "Exception in thread \"" + Thread.currentThread().getName() + "\"!", t);
}
}
}
}
});
}
}
for(EaglerListenerConfig lst : conf.getServerListeners()) {
HttpWebServer srv = lst.getWebServer();
@ -115,46 +239,13 @@ public class EaglerPipeline {
}
}
}
if(!conf.getUpdateConfig().isBlockAllClientUpdates()) {
int sizeTracker = 0;
for(Channel c : channelsList) {
EaglerConnectionInstance conn = c.attr(EaglerPipeline.CONNECTION_INSTANCE).get();
if(conn.userConnection == null) {
continue;
}
EaglerInitialHandler i = (EaglerInitialHandler)conn.userConnection.getPendingConnection();
ClientCertificateHolder certHolder = null;
synchronized(i.certificatesToSend) {
if(i.certificatesToSend.size() > 0) {
Iterator<ClientCertificateHolder> itr = i.certificatesToSend.iterator();
certHolder = itr.next();
itr.remove();
}
}
if(certHolder != null) {
int identityHash = certHolder.hashCode();
boolean bb;
synchronized(i.certificatesSent) {
bb = i.certificatesSent.add(identityHash);
}
if(bb) {
conn.userConnection.sendData(UPDATE_CERT_CHANNEL, certHolder.data);
sizeTracker += certHolder.data.length;
if(sizeTracker > (conf.getUpdateConfig().getCertPacketDataRateLimit() / 4)) {
break;
}
}
}
}
EaglerUpdateSvc.updateTick();
}
}catch(Throwable t) {
log.severe("Exception in thread \"" + Thread.currentThread().getName() + "\"! " + t.toString());
t.printStackTrace();
}
}
};
public static final ChannelInitializer<Channel> SERVER_CHILD = new ChannelInitializer<Channel>() {
@Override
@ -194,5 +285,34 @@ public class EaglerPipeline {
openChannels.remove(channel);
}
}
public static void addServerConnectListener(UserConnection player) {
synchronized(waitingServerConnections) {
waitingServerConnections.add(player);
}
}
private static void handleServerConnectionReady(UserConnection userConnection) {
try {
ServerConnection server = userConnection.getServer();
server.sendData(EaglerBackendRPCProtocol.CHANNEL_NAME_READY, ArrayUtils.EMPTY_BYTE_ARRAY);
if(userConnection.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler handler = (EaglerInitialHandler) userConnection.getPendingConnection();
ServerInfo sv = server.getInfo();
EaglerXBungee plugin = EaglerXBungee.getEagler();
boolean fnawSkins = !plugin.getConfig().getDisableFNAWSkinsEverywhere()
&& !plugin.getConfig().getDisableFNAWSkinsOnServersSet().contains(sv.getName());
if(fnawSkins != handler.currentFNAWSkinEnableStatus.getAndSet(fnawSkins)) {
handler.sendEaglerMessage(new SPacketEnableFNAWSkinsEAG(fnawSkins, false));
}
if(handler.getEaglerListenerConfig().getEnableVoiceChat()) {
plugin.getVoiceService().handleServerConnected(userConnection, sv);
}
}
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Failed to process server connection ready handler for player \""
+ userConnection.getName() + "\"", t);
}
}
}

View File

@ -0,0 +1,64 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Protocol;
/**
* 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
* 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 EaglerProtocolAccessProxy {
private static final Field fieldToClient;
private static final Field fieldToServer;
private static final Method methodGetId;
private static final Method methodCreatePacket;
static {
try {
fieldToClient = Protocol.class.getDeclaredField("TO_CLIENT");
fieldToClient.setAccessible(true);
fieldToServer = Protocol.class.getDeclaredField("TO_SERVER");
fieldToServer.setAccessible(true);
methodGetId = Protocol.DirectionData.class.getDeclaredMethod("getId", Class.class, int.class);
methodGetId.setAccessible(true);
methodCreatePacket = Protocol.DirectionData.class.getDeclaredMethod("createPacket", int.class, int.class);
methodCreatePacket.setAccessible(true);
}catch(Throwable t) {
throw new RuntimeException(t);
}
}
public static int getPacketId(Protocol protocol, int protocolVersion, DefinedPacket pkt, boolean server) {
try {
Object prot = server ? fieldToClient.get(protocol) : fieldToServer.get(protocol);
return (int)methodGetId.invoke(prot, pkt.getClass(), protocolVersion);
}catch(Throwable t) {
throw new RuntimeException(t);
}
}
public static DefinedPacket createPacket(Protocol protocol, int protocolVersion, int packetId, boolean server) {
try {
Object prot = server ? fieldToClient.get(protocol) : fieldToServer.get(protocol);
return (DefinedPacket) methodCreatePacket.invoke(prot, packetId, protocolVersion);
}catch(Throwable t) {
throw new RuntimeException(t);
}
}
}

View File

@ -20,6 +20,7 @@ import java.util.Set;
import java.util.logging.Logger;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.SHA1Digest;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerUpdateConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler.ClientCertificateHolder;
@ -43,9 +44,9 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
*/
public class EaglerUpdateSvc {
private static final List<ClientCertificateHolder> certs = new ArrayList();
private static final Map<String,CachedClientCertificate> certsCache = new HashMap();
private static final Set<String> deadURLS = new HashSet();
private static final List<ClientCertificateHolder> certs = new ArrayList<>();
private static final Map<String,CachedClientCertificate> certsCache = new HashMap<>();
private static final Set<String> deadURLS = new HashSet<>();
private static class CachedClientCertificate {
private final ClientCertificateHolder cert;
@ -61,7 +62,7 @@ public class EaglerUpdateSvc {
public static void updateTick() {
Logger log = EaglerXBungee.logger();
long millis = System.currentTimeMillis();
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
EaglerUpdateConfig conf = EaglerXBungee.getEagler().getConfig().getUpdateConfig();
if(conf.isDownloadLatestCerts() && millis - lastDownload > (long)conf.getCheckForUpdatesEvery() * 1000l) {
lastDownload = millis;
@ -72,7 +73,7 @@ public class EaglerUpdateSvc {
log.severe("Uncaught exception downloading certificates!");
t.printStackTrace();
}
millis = System.currentTimeMillis();
millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
}
if(conf.isEnableEagcertFolder() && millis - lastEnumerate > 5000l) {
lastEnumerate = millis;
@ -95,7 +96,7 @@ public class EaglerUpdateSvc {
return;
}
}
Set<String> filenames = new HashSet();
Set<String> filenames = new HashSet<>();
for(String str : conf.getDownloadCertURLs()) {
try {
URL url = new URL(str);
@ -179,7 +180,7 @@ public class EaglerUpdateSvc {
}
boolean dirty = false;
File[] dirList = eagcert.listFiles();
Set<String> existingFiles = new HashSet();
Set<String> existingFiles = new HashSet<>();
for(int i = 0; i < dirList.length; ++i) {
File f = dirList[i];
String n = f.getName();

View File

@ -23,11 +23,14 @@ import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.ReferenceCountUtil;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftWebSocketOpenEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerRateLimiter;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.RateLimitStatus;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpMemoryCache;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.event.ClientConnectEvent;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
@ -63,12 +66,20 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
String rateLimitHost = null;
SocketAddress addr;
if(conf.isForwardIp()) {
String str = headers.get(conf.getForwardIpHeader());
if(str != null) {
rateLimitHost = str.split(",", 2)[0];
try {
ctx.channel().attr(EaglerPipeline.REAL_ADDRESS).set(InetAddress.getByName(rateLimitHost));
InetAddress inetAddr = InetAddress.getByName(rateLimitHost);
addr = ctx.channel().remoteAddress();
if(addr instanceof InetSocketAddress) {
addr = new InetSocketAddress(inetAddr, ((InetSocketAddress)addr).getPort());
}else {
addr = new InetSocketAddress(inetAddr, 0);
}
ctx.channel().attr(EaglerPipeline.REAL_ADDRESS).set(inetAddr);
}catch(UnknownHostException ex) {
EaglerXBungee.logger().warning("[" + ctx.channel().remoteAddress() + "]: Connected with an invalid '" + conf.getForwardIpHeader() + "' header, disconnecting...");
ctx.close();
@ -80,7 +91,7 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
return;
}
}else {
SocketAddress addr = ctx.channel().remoteAddress();
addr = ctx.channel().remoteAddress();
if(addr instanceof InetSocketAddress) {
rateLimitHost = ((InetSocketAddress) addr).getAddress().getHostAddress();
}
@ -98,17 +109,31 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
return;
}
ClientConnectEvent evt = BungeeCord.getInstance().getPluginManager().callEvent(new ClientConnectEvent(addr, conf));
if(evt.isCancelled()) {
ctx.close();
return;
}
if(headers.get(HttpHeaderNames.CONNECTION) != null && headers.get(HttpHeaderNames.CONNECTION).toLowerCase().contains("upgrade") &&
"websocket".equalsIgnoreCase(headers.get(HttpHeaderNames.UPGRADE))) {
String origin = headers.get(HttpHeaderNames.ORIGIN);
if(origin != null) {
ctx.channel().attr(EaglerPipeline.ORIGIN).set(origin);
}
//TODO: origin blacklist
String userAgent = headers.get(HttpHeaderNames.USER_AGENT);
if(userAgent != null) {
ctx.channel().attr(EaglerPipeline.USER_AGENT).set(userAgent);
}
if(ipRateLimit == RateLimitStatus.OK) {
EaglercraftWebSocketOpenEvent evt2 = new EaglercraftWebSocketOpenEvent(ctx.channel(), conf, rateLimitHost, origin, userAgent);
BungeeCord.getInstance().getPluginManager().callEvent(evt2);
if(evt2.isCancelled()) {
ctx.close();
return;
}
ctx.channel().attr(EaglerPipeline.HOST).set(headers.get(HttpHeaderNames.HOST));
ctx.pipeline().replace(this, "HttpWebSocketHandler", new HttpWebSocketHandler(conf));
}

View File

@ -72,6 +72,7 @@ public abstract class HttpServerQueryHandler extends ChannelInboundHandlerAdapte
private boolean acceptBinaryPacket = false;
private boolean hasClosed = false;
private boolean keepAlive = false;
private long maxAge = -1l;
public void beginHandleQuery(EaglerListenerConfig conf, ChannelHandlerContext context, String accept) {
this.conf = conf;
@ -188,6 +189,14 @@ public abstract class HttpServerQueryHandler extends ChannelInboundHandlerAdapte
return accept;
}
public String getOrigin() {
return context.channel().attr(EaglerPipeline.ORIGIN).get();
}
public String getUserAgent() {
return context.channel().attr(EaglerPipeline.USER_AGENT).get();
}
public void sendStringResponse(String type, String str) {
context.writeAndFlush(new TextWebSocketFrame(QueryManager.createStringResponse(accept, str).toString()));
}
@ -220,6 +229,14 @@ public abstract class HttpServerQueryHandler extends ChannelInboundHandlerAdapte
return keepAlive;
}
public long getMaxAge() {
return maxAge;
}
public void setMaxAge(long millis) {
this.maxAge = millis;
}
protected abstract void begin(String queryType);
protected abstract void processString(String str);

View File

@ -10,12 +10,15 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.apache.commons.codec.binary.Base64;
import com.google.common.collect.Sets;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@ -39,10 +42,11 @@ import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftClientBrandEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftHandleAuthCookieEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftHandleAuthPasswordEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftIsAuthRequiredEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftIsAuthRequiredEvent.AuthMethod;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftIsAuthRequiredEvent.AuthResponse;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftMOTDEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftRegisterCapeEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftRegisterSkinEvent;
@ -55,12 +59,14 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerRate
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerUpdateConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.RateLimitStatus;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler.ClientCertificateHolder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerBungeeProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol.GameProtocolMessageController;
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.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.AbstractReconnectHandler;
@ -73,9 +79,9 @@ import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.connection.UpstreamBridge;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.event.ServerConnectEvent;
@ -103,17 +109,19 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
private int clientProtocolVersion = -1;
private boolean isProtocolExchanged = false;
private int gameProtocolVersion = -1;
private CharSequence clientBrandString;
private CharSequence clientVersionString;
private CharSequence clientUsername;
private String clientBrandString;
private String clientVersionString;
private String clientUsername;
private UUID clientUUID;
private UUID offlineUUID;
private CharSequence clientRequestedServer;
private String clientRequestedServer;
private boolean clientAuth;
private byte[] clientAuthUsername;
private byte[] clientAuthPassword;
private boolean clientEnableCookie;
private byte[] clientCookieData;
private EaglercraftIsAuthRequiredEvent authRequireEvent;
private final Map<String, byte[]> profileData = new HashMap();
private final Map<String, byte[]> profileData = new HashMap<>();
private boolean hasFirstPacket = false;
private boolean hasBinaryConnection = false;
private boolean connectionClosed = false;
@ -122,6 +130,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
private Property texturesOverrideProperty;
private boolean overrideEaglerToVanillaSkins;
private static final Set<String> profileDataStandard = Sets.newHashSet(
"skin_v1", "skin_v2", "cape_v1", "update_cert_v1", "brand_uuid_v1");
public HttpWebSocketHandler(EaglerListenerConfig conf) {
this.conf = conf;
}
@ -171,7 +182,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
int limit = bungus.config.getPlayerLimit();
if (limit > 0 && bungus.getOnlineCount() >= limit) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, bungus.getTranslation("proxy_full"))
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}
@ -185,7 +196,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if (i >= conf.getMaxPlayer()) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, bungus.getTranslation("proxy_full"))
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}
}
@ -260,12 +271,12 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if(eaglerLegacyProtocolVersion == 1) {
if(authConfig.isEnableAuthentication()) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Please update your client to register on this server!")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}else if(buffer.readUnsignedByte() != minecraftProtocolVersion) {
}else if(buffer.readUnsignedByte() != minecraftProtocolVersion || !conf.isAllowV3()) {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
connectionClosed = true;
ByteBuf buf = Unpooled.buffer();
ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_VERSION_MISMATCH);
buf.writeByte(1);
buf.writeByte(1);
@ -277,32 +288,38 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
return;
}
}else if(eaglerLegacyProtocolVersion == 2) {
int minProtVers = Integer.MAX_VALUE;
int maxProtVers = -1;
boolean hasV2InList = false;
boolean hasV3InList = false;
int minGameVers = Integer.MAX_VALUE;
int maxGameVers = -1;
boolean has47InList = false;
//make sure to update VersionQueryHandler too
int minServerSupported = conf.isAllowV3() ? 2 : 4;
int maxServerSupported = conf.isAllowV4() ? 4 : 3;
int minAvailableProtVers = Integer.MAX_VALUE;
int maxAvailableProtVers = Integer.MIN_VALUE;
int minSupportedProtVers = Integer.MAX_VALUE;
int maxSupportedProtVers = Integer.MIN_VALUE;
int cnt = buffer.readUnsignedShort();
for(int i = 0; i < cnt; ++i) {
int j = buffer.readUnsignedShort();
if(j == 2) {
hasV2InList = true;
if(j > maxAvailableProtVers) {
maxAvailableProtVers = j;
}
if(j == 3) {
hasV3InList = true;
if(j < minAvailableProtVers) {
minAvailableProtVers = j;
}
if(j > maxProtVers) {
maxProtVers = j;
}
if(j < minProtVers) {
minProtVers = j;
if(j >= minServerSupported && j <= maxServerSupported) {
if(j > maxSupportedProtVers) {
maxSupportedProtVers = j;
}
if(j < minSupportedProtVers) {
minSupportedProtVers = j;
}
}
}
int minGameVers = Integer.MAX_VALUE;
int maxGameVers = -1;
boolean has47InList = false;
cnt = buffer.readUnsignedShort();
for(int i = 0; i < cnt; ++i) {
int j = buffer.readUnsignedShort();
@ -317,34 +334,41 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}
}
if(minProtVers == Integer.MAX_VALUE || minGameVers == Integer.MAX_VALUE) {
if(maxAvailableProtVers == Integer.MIN_VALUE || maxGameVers == Integer.MIN_VALUE) {
throw new IOException();
}
boolean versMisMatch = false;
boolean isServerProbablyOutdated = false;
boolean isClientProbablyOutdated = false;
if(!hasV2InList && !hasV3InList) {
if(maxSupportedProtVers == Integer.MIN_VALUE) {
clientProtocolVersion = maxAvailableProtVers < 3 ? 2 : 3;
versMisMatch = true;
isServerProbablyOutdated = minProtVers > 3 && maxProtVers > 3; //make sure to update VersionQueryHandler too
isClientProbablyOutdated = minProtVers < 2 && maxProtVers < 2;
isServerProbablyOutdated = minAvailableProtVers > maxServerSupported && maxAvailableProtVers > maxServerSupported;
isClientProbablyOutdated = minAvailableProtVers < minServerSupported && maxAvailableProtVers < minServerSupported;
}else if(!has47InList) {
clientProtocolVersion = 3;
versMisMatch = true;
isServerProbablyOutdated = minGameVers > minecraftProtocolVersion && maxGameVers > minecraftProtocolVersion;
isClientProbablyOutdated = minGameVers < minecraftProtocolVersion && maxGameVers < minecraftProtocolVersion;
}else {
clientProtocolVersion = maxSupportedProtVers;
}
clientProtocolVersion = hasV3InList ? 3 : 2;
if(versMisMatch) {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
connectionClosed = true;
ByteBuf buf = Unpooled.buffer();
ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_VERSION_MISMATCH);
buf.writeShort(2);
buf.writeShort(2); // want v2 or v3
buf.writeShort(3);
buf.writeShort((conf.isAllowV3() ? 2 : 0) + (conf.isAllowV4() ? 1 : 0));
if(conf.isAllowV3()) {
buf.writeShort(2);
buf.writeShort(3);
}
if(conf.isAllowV4()) {
buf.writeShort(4);
}
buf.writeShort(1);
buf.writeShort(minecraftProtocolVersion); // want game version 47
@ -357,14 +381,14 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}
}else {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Legacy protocol version should always be '2' on post-snapshot clients")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}
int strlen = buffer.readUnsignedByte();
CharSequence eaglerBrand = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII);
String eaglerBrand = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
strlen = buffer.readUnsignedByte();
CharSequence eaglerVersionString = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII);
String eaglerVersionString = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
if(eaglerLegacyProtocolVersion >= 2) {
clientAuth = buffer.readBoolean();
@ -393,6 +417,19 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
addr = InetAddress.getLoopbackAddress();
}
}
EaglercraftClientBrandEvent brandEvent = new EaglercraftClientBrandEvent(eaglerBrand, eaglerVersionString,
ctx.channel().attr(EaglerPipeline.ORIGIN).get(), clientProtocolVersion, addr);
eaglerXBungee.getProxy().getPluginManager().callEvent(brandEvent);
if(brandEvent.isCancelled()) {
BaseComponent kickReason = brandEvent.getMessage();
if(kickReason == null) {
kickReason = new TextComponent("End of stream");
}
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, kickReason)
.addListener(ChannelFutureListener.CLOSE);
return;
}
final boolean final_useSnapshotFallbackProtocol = useSnapshotFallbackProtocol;
Runnable continueThread = () -> {
@ -401,7 +438,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
clientBrandString = eaglerBrand;
clientVersionString = eaglerVersionString;
ByteBuf buf = Unpooled.buffer();
ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_VERSION);
if(final_useSnapshotFallbackProtocol) {
@ -426,8 +463,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
int meth = getAuthMethodId(authRequireEvent.getUseAuthType());
if(meth == -1) {
buf.release();
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Unsupported authentication method resolved")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
EaglerXBungee.logger().severe("[" + localAddrString + "]: Disconnecting, unsupported AuthMethod: " + authRequireEvent.getUseAuthType());
return;
}
@ -455,28 +493,28 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
clientAuth, clientAuthUsername, (reqAuthEvent) -> {
if(authRequireEvent.shouldKickUser()) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, authRequireEvent.getKickMessage())
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}
AuthResponse resp = authRequireEvent.getAuthRequired();
EaglercraftIsAuthRequiredEvent.AuthResponse resp = authRequireEvent.getAuthRequired();
if(resp == null) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "IsAuthRequiredEvent was not handled")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
EaglerXBungee.logger().severe("[" + localAddrString + "]: Disconnecting, no installed authentication system handled: " + authRequireEvent.toString());
return;
}
if(resp == AuthResponse.DENY) {
if(resp == EaglercraftIsAuthRequiredEvent.AuthResponse.DENY) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, authRequireEvent.getKickMessage())
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}
AuthMethod type = authRequireEvent.getUseAuthType();
EaglercraftIsAuthRequiredEvent.AuthMethod type = authRequireEvent.getUseAuthType();
if(type == null) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "IsAuthRequiredEvent was not fully handled")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
EaglerXBungee.logger().severe("[" + localAddrString + "]: Disconnecting, no authentication method provided by handler");
return;
}
@ -484,12 +522,12 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
int typeId = getAuthMethodId(type);
if(typeId == -1) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Unsupported authentication method resolved")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
EaglerXBungee.logger().severe("[" + localAddrString + "]: Disconnecting, unsupported AuthMethod: " + type);
return;
}
if(!clientAuth && resp == AuthResponse.REQUIRE) {
if(!clientAuth && resp == EaglercraftIsAuthRequiredEvent.AuthResponse.REQUIRE) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_AUTHENTICATION_REQUIRED,
HandshakePacketTypes.AUTHENTICATION_REQUIRED + " [" + typeId + "] " + authRequireEvent.getAuthMessage())
.addListener(ChannelFutureListener.CLOSE);
@ -498,7 +536,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}else {
if(authRequireEvent.getUseAuthType() == null) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "IsAuthRequiredEvent was not fully handled")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
EaglerXBungee.logger().severe("[" + localAddrString + "]: Disconnecting, no authentication method provided by handler");
return;
}
@ -535,10 +573,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
clientLoginState = HandshakePacketTypes.STATE_STALLING;
int strlen = buffer.readUnsignedByte();
clientUsername = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII);
clientUsername = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
String usrs = clientUsername.toString();
if(!usrs.equals(usrs.replaceAll("[^A-Za-z0-9_]", "_").trim())) {
if(!clientUsername.equals(clientUsername.replaceAll("[^A-Za-z0-9_]", "_"))) {
sendLoginDenied(ctx, "Invalid characters in username")
.addListener(ChannelFutureListener.CLOSE);
return;
@ -566,11 +603,28 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
clientUUID = offlineUUID = EaglerInitialHandler.generateOfflineUUID(clientAuthUsername);
strlen = buffer.readUnsignedByte();
clientRequestedServer = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII);
clientRequestedServer = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
strlen = buffer.readUnsignedByte();
clientAuthPassword = new byte[strlen];
buffer.readBytes(clientAuthPassword);
if(clientProtocolVersion >= 4) {
clientEnableCookie = buffer.readBoolean();
strlen = buffer.readUnsignedByte();
if(clientEnableCookie && strlen > 0) {
clientCookieData = new byte[strlen];
buffer.readBytes(clientCookieData);
}else {
if(strlen > 0) {
throw new IllegalArgumentException("Unexpected cookie");
}
clientCookieData = null;
}
}else {
clientEnableCookie = false;
clientCookieData = null;
}
if(buffer.isReadable()) {
throw new IllegalArgumentException("Packet too long");
}
@ -578,16 +632,14 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
Runnable continueThread = () -> {
final BungeeCord bungee = BungeeCord.getInstance();
String usernameStr = clientUsername.toString();
final ProxiedPlayer oldName = bungee.getPlayer(usernameStr);
final ProxiedPlayer oldName = bungee.getPlayer(clientUsername);
if (oldName != null) {
sendLoginDenied(ctx, bungee.getTranslation("already_connected_proxy", new Object[0]))
.addListener(ChannelFutureListener.CLOSE);
sendLoginDenied(ctx, bungee.getTranslation("already_connected_proxy")).addListener(ChannelFutureListener.CLOSE);
return;
}
clientLoginState = HandshakePacketTypes.STATE_CLIENT_LOGIN;
ByteBuf buf = Unpooled.buffer();
ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_ALLOW_LOGIN);
buf.writeByte(clientUsername.length());
buf.writeCharSequence(clientUsername, StandardCharsets.US_ASCII);
@ -599,93 +651,172 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
EaglerXBungee eaglerXBungee = EaglerXBungee.getEagler();
EaglerAuthConfig authConfig = eaglerXBungee.getConfig().getAuthConfig();
if(authConfig.isEnableAuthentication() && clientAuth) {
if(clientAuthPassword.length == 0) {
sendLoginDenied(ctx, "Client provided no authentication code")
.addListener(ChannelFutureListener.CLOSE);
return;
}else {
try {
EaglercraftHandleAuthPasswordEvent handleEvent = new EaglercraftHandleAuthPasswordEvent(
conf, remoteAddress, authRequireEvent.getOriginHeader(), clientAuthUsername,
authRequireEvent.getSaltingData(), clientUsername, clientUUID, clientAuthPassword,
authRequireEvent.getUseAuthType(), authRequireEvent.getAuthMessage(),
(Object) authRequireEvent.getAuthAttachment(), clientRequestedServer.toString(),
(handleAuthEvent) -> {
if(handleAuthEvent.getLoginAllowed() != EaglercraftHandleAuthPasswordEvent.AuthResponse.ALLOW) {
sendLoginDenied(ctx, handleAuthEvent.getLoginDeniedMessage()).addListener(ChannelFutureListener.CLOSE);
return;
}
clientUsername = handleAuthEvent.getProfileUsername();
clientUUID = handleAuthEvent.getProfileUUID();
String texPropOverrideValue = handleAuthEvent.getApplyTexturesPropertyValue();
if(texPropOverrideValue != null) {
String texPropOverrideSig = handleAuthEvent.getApplyTexturesPropertySignature();
texturesOverrideProperty = new Property("textures", texPropOverrideValue, texPropOverrideSig);
}
overrideEaglerToVanillaSkins = handleAuthEvent.isOverrideEaglerToVanillaSkins();
if(authConfig.isEnableAuthentication()) {
if(clientAuth && clientAuthPassword.length > 0) {
EaglercraftHandleAuthPasswordEvent handleEvent = new EaglercraftHandleAuthPasswordEvent(
conf, remoteAddress, authRequireEvent.getOriginHeader(), clientAuthUsername,
authRequireEvent.getSaltingData(), clientUsername, clientUUID,
clientAuthPassword, clientEnableCookie, clientCookieData,
authRequireEvent.getUseAuthType(), authRequireEvent.getAuthMessage(),
(Object) authRequireEvent.getAuthAttachment(), clientRequestedServer,
(handleAuthEvent) -> {
if(handleAuthEvent.getLoginAllowed() != EaglercraftHandleAuthPasswordEvent.AuthResponse.ALLOW) {
sendLoginDenied(ctx, handleAuthEvent.getLoginDeniedMessage()).addListener(ChannelFutureListener.CLOSE);
return;
}
clientUsername = handleAuthEvent.getProfileUsername();
clientUUID = handleAuthEvent.getProfileUUID();
String texPropOverrideValue = handleAuthEvent.getApplyTexturesPropertyValue();
if(texPropOverrideValue != null) {
String texPropOverrideSig = handleAuthEvent.getApplyTexturesPropertySignature();
texturesOverrideProperty = new Property("textures", texPropOverrideValue, texPropOverrideSig);
}
overrideEaglerToVanillaSkins = handleAuthEvent.isOverrideEaglerToVanillaSkins();
continueThread.run();
});
if(authConfig.isUseBuiltInAuthentication()) {
DefaultAuthSystem authSystem = eaglerXBungee.getAuthService();
if(authSystem != null) {
authSystem.handleAuthPasswordEvent(handleEvent);
}
}else {
eaglerXBungee.getProxy().getPluginManager().callEvent(handleEvent);
}
if(!handleEvent.isAsyncContinue()) {
handleEvent.doDirectContinue();
}
}else if(authRequireEvent.getEnableCookieAuth()) {
EaglercraftHandleAuthCookieEvent handleEvent = new EaglercraftHandleAuthCookieEvent(
conf, remoteAddress, authRequireEvent.getOriginHeader(), clientAuthUsername,
clientUsername, clientUUID, clientEnableCookie, clientCookieData,
authRequireEvent.getUseAuthType(), authRequireEvent.getAuthMessage(),
(Object) authRequireEvent.getAuthAttachment(),
clientRequestedServer, (handleAuthEvent) -> {
EaglercraftHandleAuthCookieEvent.AuthResponse resp = handleAuthEvent.getLoginAllowed();
if(resp == null) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "EaglercraftHandleAuthCookieEvent was not handled")
.addListener(ChannelFutureListener.CLOSE);
EaglerXBungee.logger().severe("[" + localAddrString + "]: Disconnecting, no installed authentication system handled: " + handleAuthEvent.toString());
return;
}
if(resp == EaglercraftHandleAuthCookieEvent.AuthResponse.DENY) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, handleAuthEvent.getLoginDeniedMessage())
.addListener(ChannelFutureListener.CLOSE);
return;
}
clientUsername = handleAuthEvent.getProfileUsername();
clientUUID = handleAuthEvent.getProfileUUID();
String texPropOverrideValue = handleAuthEvent.getApplyTexturesPropertyValue();
if(texPropOverrideValue != null) {
String texPropOverrideSig = handleAuthEvent.getApplyTexturesPropertySignature();
texturesOverrideProperty = new Property("textures", texPropOverrideValue, texPropOverrideSig);
}
overrideEaglerToVanillaSkins = handleAuthEvent.isOverrideEaglerToVanillaSkins();
if(resp == EaglercraftHandleAuthCookieEvent.AuthResponse.ALLOW) {
continueThread.run();
});
return;
}
if(authConfig.isUseBuiltInAuthentication()) {
DefaultAuthSystem authSystem = eaglerXBungee.getAuthService();
if(authSystem != null) {
authSystem.handleAuthPasswordEvent(handleEvent);
}
if(!clientAuth && resp == EaglercraftHandleAuthCookieEvent.AuthResponse.REQUIRE_AUTH) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_AUTHENTICATION_REQUIRED, HandshakePacketTypes.AUTHENTICATION_REQUIRED
+ " [" + getAuthMethodId(authRequireEvent.getUseAuthType()) + "] " + authRequireEvent.getAuthMessage())
.addListener(ChannelFutureListener.CLOSE);
EaglerXBungee.logger().info("[" + localAddrString + "]: Displaying authentication screen");
return;
}else {
eaglerXBungee.getProxy().getPluginManager().callEvent(handleEvent);
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Failed to handle authentication!")
.addListener(ChannelFutureListener.CLOSE);
return;
}
if(!handleEvent.isAsyncContinue()) {
handleEvent.doDirectContinue();
}
}catch(Throwable t) {
throw new EventException(t);
});
eaglerXBungee.getProxy().getPluginManager().callEvent(handleEvent);
if(!handleEvent.isAsyncContinue()) {
handleEvent.doDirectContinue();
}
}else {
if(authRequireEvent.getAuthRequired() != EaglercraftIsAuthRequiredEvent.AuthResponse.SKIP) {
sendLoginDenied(ctx, "Client provided no authentication code").addListener(ChannelFutureListener.CLOSE);
return;
}else {
continueThread.run();
}
}
}else {
continueThread.run();
}
}else {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
sendErrorWrong(ctx, op, "STATE_CLIENT_VERSION")
.addListener(ChannelFutureListener.CLOSE);
sendErrorWrong(ctx, op, "STATE_CLIENT_VERSION").addListener(ChannelFutureListener.CLOSE);
}
}
break;
case HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA: {
if(clientLoginState == HandshakePacketTypes.STATE_CLIENT_LOGIN) {
if(profileData.size() > 12) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_EXCESSIVE_PROFILE_DATA, "Too many profile data packets recieved")
.addListener(ChannelFutureListener.CLOSE);
return;
}
int strlen = buffer.readUnsignedByte();
String dataType = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
strlen = buffer.readUnsignedShort();
byte[] readData = new byte[strlen];
buffer.readBytes(readData);
if(buffer.isReadable()) {
throw new IllegalArgumentException("Packet too long");
}
if(!profileData.containsKey(dataType)) {
profileData.put(dataType, readData);
if(clientProtocolVersion <= 3) {
if(profileData.size() >= 12) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_EXCESSIVE_PROFILE_DATA, "Too many profile data packets recieved")
.addListener(ChannelFutureListener.CLOSE);
return;
}
int strlen = buffer.readUnsignedByte();
String dataType = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
strlen = buffer.readUnsignedShort();
byte[] readData = new byte[strlen];
buffer.readBytes(readData);
if(buffer.isReadable()) {
throw new IllegalArgumentException("Packet too long");
}
if(!profileData.containsKey(dataType)) {
profileData.put(dataType, readData);
}else {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_DUPLICATE_PROFILE_DATA, "Multiple profile data packets of the same type recieved")
.addListener(ChannelFutureListener.CLOSE);
return;
}
}else {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_DUPLICATE_PROFILE_DATA, "Multiple profile data packets of the same type recieved")
.addListener(ChannelFutureListener.CLOSE);
return;
int count = buffer.readUnsignedByte();
if(profileData.size() + count > 12) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_EXCESSIVE_PROFILE_DATA, "Too many profile data packets recieved")
.addListener(ChannelFutureListener.CLOSE);
return;
}
for(int i = 0; i < count; ++i) {
int strlen = buffer.readUnsignedByte();
String dataType = buffer.readCharSequence(strlen, StandardCharsets.US_ASCII).toString();
strlen = buffer.readUnsignedShort();
byte[] readData = new byte[strlen];
buffer.readBytes(readData);
if(!profileData.containsKey(dataType)) {
profileData.put(dataType, readData);
}else {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_DUPLICATE_PROFILE_DATA, "Multiple profile data packets of the same type recieved")
.addListener(ChannelFutureListener.CLOSE);
return;
}
}
if(buffer.isReadable()) {
throw new IllegalArgumentException("Packet too long");
}
}
}else {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
sendErrorWrong(ctx, op, "STATE_CLIENT_LOGIN").addListener(ChannelFutureListener.CLOSE);
@ -710,7 +841,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
default:
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_UNKNOWN_PACKET, "Unknown Packet #" + op)
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
break;
}
}catch(Throwable ex) {
@ -731,7 +862,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
int limit = bungee.config.getPlayerLimit();
if (limit > 0 && bungee.getOnlineCount() >= limit) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, bungee.getTranslation("proxy_full"))
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}
@ -745,7 +876,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if (i >= conf.getMaxPlayer()) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, bungee.getTranslation("proxy_full"))
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}
}
@ -754,17 +885,17 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
final ProxiedPlayer oldName = bungee.getPlayer(usernameStr);
if (oldName != null) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE,
bungee.getTranslation("already_connected_proxy", new Object[0]))
.addListener(ChannelFutureListener.CLOSE);
bungee.getTranslation("already_connected_proxy")).addListener(ChannelFutureListener.CLOSE);
return;
}
final ChannelWrapper ch = new EaglerChannelWrapper(ctx);
final EaglerChannelWrapper ch = new EaglerChannelWrapper(ctx);
InetSocketAddress baseAddress = (InetSocketAddress)ctx.channel().remoteAddress();
InetAddress addr = ctx.channel().attr(EaglerPipeline.REAL_ADDRESS).get();
if(addr != null) {
baseAddress = new InetSocketAddress(addr, baseAddress.getPort());
ch.setRemoteAddress(baseAddress);
}
EaglerUpdateConfig updateconf = EaglerXBungee.getEagler().getConfig().getUpdateConfig();
boolean blockUpdate = updateconf.isBlockAllClientUpdates();
ClientCertificateHolder cert = null;
@ -774,9 +905,34 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
EaglerUpdateSvc.sendCertificateToPlayers(cert = EaglerUpdateSvc.tryMakeHolder(b));
}
}
final EaglerInitialHandler initialHandler = new EaglerInitialHandler(bungee, conf, ch, gameProtocolVersion,
UUID clientBrandUUID = null;
String clientBrandAsString = clientBrandString.toString();
byte[] brandUUIDBytes = profileData.get("brand_uuid_v1");
if(brandUUIDBytes != null) {
if(brandUUIDBytes.length == 16) {
ByteBuf buf = Unpooled.wrappedBuffer(brandUUIDBytes);
clientBrandUUID = new UUID(buf.readLong(), buf.readLong());
if (clientBrandUUID.equals(EaglerXBungeeAPIHelper.BRAND_NULL_UUID)
|| clientBrandUUID.equals(EaglerXBungeeAPIHelper.BRAND_PENDING_UUID)
|| clientBrandUUID.equals(EaglerXBungeeAPIHelper.BRAND_VANILLA_UUID)) {
clientBrandUUID = null;
}
}
}else {
clientBrandUUID = EaglerXBungeeAPIHelper.makeClientBrandUUIDLegacy(clientBrandAsString);
}
Map<String,byte[]> otherProfileData = new HashMap<>();
for(Entry<String,byte[]> etr2 : profileData.entrySet()) {
String str = etr2.getKey();
if(!profileDataStandard.contains(str)) {
otherProfileData.put(str, etr2.getValue());
}
}
final EaglerInitialHandler initialHandler = new EaglerInitialHandler(bungee, conf, ch, clientProtocolVersion,
gameProtocolVersion, clientBrandAsString, clientVersionString.toString(), clientBrandUUID,
usernameStr, clientUUID, offlineUUID, baseAddress, ctx.channel().attr(EaglerPipeline.HOST).get(),
ctx.channel().attr(EaglerPipeline.ORIGIN).get(), cert);
ctx.channel().attr(EaglerPipeline.ORIGIN).get(), ctx.channel().attr(EaglerPipeline.USER_AGENT).get(),
cert, clientEnableCookie, clientCookieData, otherProfileData);
if(!blockUpdate) {
List<ClientCertificateHolder> set = EaglerUpdateSvc.getCertList();
synchronized(set) {
@ -794,10 +950,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
final Callback<LoginEvent> complete = (Callback<LoginEvent>) new Callback<LoginEvent>() {
public void done(final LoginEvent result, final Throwable error) {
if (result.isCancelled()) {
final BaseComponent[] reason = result.getCancelReasonComponents();
final BaseComponent reason = result.getReason();
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE,
ComponentSerializer.toString(reason != null ? reason
: TextComponent.fromLegacyText(bungee.getTranslation("kick_message", new Object[0]))))
reason != null ? reason : TextComponent.fromLegacy(bungee.getTranslation("kick_message")))
.addListener(ChannelFutureListener.CLOSE);
return;
}
@ -805,7 +960,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
return;
}
ByteBuf buf = Unpooled.buffer();
ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_FINISH_LOGIN);
ctx.writeAndFlush(new BinaryWebSocketFrame(buf)).addListener(new GenericFutureListener<Future<Void>>() {
@ -817,6 +972,10 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
final UserConnection userCon = new UserConnection(bungee, ch, usernameStr, initialHandler);
userCon.setCompressionThreshold(-1);
initialHandler.messageProtocolController = new GameProtocolMessageController(userCon,
GamePluginMessageProtocol.getByVersion(clientProtocolVersion),
GameProtocolMessageController.createServerHandler(clientProtocolVersion, userCon,
EaglerXBungee.getEagler()), conf.getDefragSendDelay());
try {
if (!userCon.init()) {
userCon.disconnect(bungee.getTranslation("already_connected_proxy"));
@ -845,19 +1004,18 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
pp.addBefore("HandlerBoss", "ReadTimeoutHandler", new ReadTimeoutHandler((BungeeCord.getInstance()).config.getTimeout(), TimeUnit.MILLISECONDS));
pp.addBefore("HandlerBoss", "EaglerMinecraftDecoder", new EaglerMinecraftDecoder(
EaglerBungeeProtocol.GAME, false, gameProtocolVersion));
pp.addBefore("HandlerBoss", "EaglerMinecraftDecoder", new EaglerMinecraftDecoder(Protocol.GAME, false, gameProtocolVersion));
pp.addBefore("HandlerBoss", "EaglerMinecraftByteBufEncoder", new EaglerMinecraftByteBufEncoder());
pp.addBefore("HandlerBoss", "EaglerMinecraftWrappedEncoder", new EaglerMinecraftWrappedEncoder());
pp.addBefore("HandlerBoss", "EaglerMinecraftEncoder", new EaglerMinecraftEncoder(
EaglerBungeeProtocol.GAME, true, gameProtocolVersion));
pp.addBefore("HandlerBoss", "EaglerMinecraftEncoder", new EaglerMinecraftEncoder(Protocol.GAME, true, gameProtocolVersion));
boolean doRegisterSkins = true;
boolean doForceSkins = false;
EaglercraftRegisterSkinEvent registerSkinEvent = new EaglercraftRegisterSkinEvent(usernameStr, clientUUID);
EaglercraftRegisterSkinEvent registerSkinEvent = new EaglercraftRegisterSkinEvent(usernameStr, clientUUID, authRequireEvent != null ? authRequireEvent.getAuthAttachment() : null);
bungee.getPluginManager().callEvent(registerSkinEvent);
@ -866,20 +1024,20 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if(prop != null) {
texturesOverrideProperty = prop;
overrideEaglerToVanillaSkins = true;
if(clientProtocolVersion >= 4 && (EaglerXBungee.getEagler().getSkinService() instanceof SkinService)) {
doForceSkins = true;
}
}else {
if(useExistingProp) {
overrideEaglerToVanillaSkins = true;
}else {
byte[] custom = registerSkinEvent.getForceSetUseCustomPacket();
if(custom != null) {
profileData.remove("skin_v2");
profileData.put("skin_v1", custom);
overrideEaglerToVanillaSkins = false;
}else {
String customUrl = registerSkinEvent.getForceSetUseURL();
if(customUrl != null) {
EaglerXBungee.getEagler().getSkinService().registerTextureToPlayerAssociation(customUrl, initialHandler.getUniqueId());
doRegisterSkins = false;
overrideEaglerToVanillaSkins = false;
if(clientProtocolVersion >= 4) {
doForceSkins = true;
}
}
}
@ -887,14 +1045,13 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
EaglerBungeeConfig eaglerConf = EaglerXBungee.getEagler().getConfig();
if(texturesOverrideProperty != null) {
LoginResult oldProfile = initialHandler.getLoginProfile();
if(oldProfile == null) {
oldProfile = new LoginResult(initialHandler.getUniqueId().toString(), initialHandler.getName(), null);
initialHandler.setLoginProfile(oldProfile);
}
oldProfile.setProperties(new Property[] { texturesOverrideProperty, EaglerBungeeConfig.isEaglerProperty });
oldProfile.setProperties(eaglerConf.getEnableIsEaglerPlayerProperty() ? new Property[] { texturesOverrideProperty, EaglerBungeeConfig.isEaglerProperty } : new Property[] { texturesOverrideProperty });
}else {
if(!useExistingProp) {
String vanillaSkin = eaglerConf.getEaglerPlayersVanillaSkin();
@ -928,6 +1085,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}
}
doRegisterSkins = false;
if(clientProtocolVersion >= 4) {
doForceSkins = true;
}
}catch(Throwable t) {
}
break;
@ -938,10 +1098,18 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}
if(doRegisterSkins) {
if(profileData.containsKey("skin_v1")) {
if(clientProtocolVersion >= 4 && profileData.containsKey("skin_v2")) {
try {
SkinPackets.registerEaglerPlayer(clientUUID, profileData.get("skin_v2"),
EaglerXBungee.getEagler().getSkinService(), 4);
} catch (Throwable ex) {
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getSkinService());
EaglerXBungee.logger().info("[" + ctx.channel().remoteAddress() + "]: Invalid skin packet: " + ex.toString());
}
}else if(profileData.containsKey("skin_v1")) {
try {
SkinPackets.registerEaglerPlayer(clientUUID, profileData.get("skin_v1"),
EaglerXBungee.getEagler().getSkinService());
EaglerXBungee.getEagler().getSkinService(), 3);
} catch (Throwable ex) {
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getSkinService());
EaglerXBungee.logger().info("[" + ctx.channel().remoteAddress() + "]: Invalid skin packet: " + ex.toString());
@ -950,8 +1118,11 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getSkinService());
}
}
if(doForceSkins) {
EaglerXBungee.getEagler().getSkinService().processForceSkin(clientUUID, initialHandler);
}
EaglercraftRegisterCapeEvent registerCapeEvent = new EaglercraftRegisterCapeEvent(usernameStr, clientUUID);
EaglercraftRegisterCapeEvent registerCapeEvent = new EaglercraftRegisterCapeEvent(usernameStr, clientUUID, authRequireEvent != null ? authRequireEvent.getAuthAttachment() : null);
bungee.getPluginManager().callEvent(registerCapeEvent);
@ -971,11 +1142,21 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}else {
CapePackets.registerEaglerPlayerFallback(clientUUID, EaglerXBungee.getEagler().getCapeService());
}
if(forceCape != null && clientProtocolVersion >= 4) {
EaglerXBungee.getEagler().getCapeService().processForceCape(clientUUID, initialHandler);
}
if(conf.getEnableVoiceChat()) {
EaglerXBungee.getEagler().getVoiceService().handlePlayerLoggedIn(userCon);
}
if(clientProtocolVersion >= 4) {
GameMessagePacket pauseMenuPkt = EaglerXBungee.getEagler().getConfig().getPauseMenuConf().getPacket();
if(pauseMenuPkt != null) {
initialHandler.sendEaglerMessage(pauseMenuPkt);
}
}
ServerInfo server;
if (bungee.getReconnectHandler() != null) {
server = bungee.getReconnectHandler().getServer((ProxiedPlayer) userCon);
@ -1015,10 +1196,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
final Callback<PreLoginEvent> completePre = new Callback<PreLoginEvent>() {
public void done(PreLoginEvent var1, Throwable var2) {
if (var1.isCancelled()) {
final BaseComponent[] reason = var1.getCancelReasonComponents();
final BaseComponent reason = var1.getReason();
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE,
ComponentSerializer.toString(reason != null ? reason
: TextComponent.fromLegacyText(bungee.getTranslation("kick_message", new Object[0]))))
reason != null ? reason : TextComponent.fromLegacy(bungee.getTranslation("kick_message")))
.addListener(ChannelFutureListener.CLOSE);
}else {
bungee.getPluginManager().callEvent(new LoginEvent(initialHandler, complete));
@ -1109,6 +1289,8 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if(!handler.isClosed() && !handler.shouldKeepAlive()) {
connectionClosed = true;
handler.close();
}else {
ctx.channel().attr(EaglerPipeline.CONNECTION_INSTANCE).get().queryHandler = handler;
}
}else {
connectionClosed = true;
@ -1121,7 +1303,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}
}
private int getAuthMethodId(AuthMethod meth) {
private int getAuthMethodId(EaglercraftIsAuthRequiredEvent.AuthMethod meth) {
switch(meth) {
case PLAINTEXT:
return 255; // plaintext authentication
@ -1136,13 +1318,13 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
private ChannelFuture sendLoginDenied(ChannelHandlerContext ctx, String reason) {
if((!isProtocolExchanged || clientProtocolVersion == 2) && reason.length() > 255) {
reason = reason.substring(0, 256);
reason = reason.substring(0, 255);
}else if(reason.length() > 65535) {
reason = reason.substring(0, 65536);
reason = reason.substring(0, 65535);
}
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
connectionClosed = true;
ByteBuf buf = Unpooled.buffer();
ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_DENY_LOGIN);
byte[] msg = reason.getBytes(StandardCharsets.UTF_8);
if(!isProtocolExchanged || clientProtocolVersion == 2) {
@ -1157,16 +1339,24 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
private ChannelFuture sendErrorWrong(ChannelHandlerContext ctx, int op, String state) {
return sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_WRONG_PACKET, "Wrong Packet #" + op + " in state '" + state + "'");
}
private ChannelFuture sendErrorCode(ChannelHandlerContext ctx, int code, BaseComponent comp) {
if((!isProtocolExchanged || clientProtocolVersion == 2)) {
return sendErrorCode(ctx, code, ComponentSerializer.toString(comp));
}else {
return sendErrorCode(ctx, code, comp.toLegacyText());
}
}
private ChannelFuture sendErrorCode(ChannelHandlerContext ctx, int code, String str) {
if((!isProtocolExchanged || clientProtocolVersion == 2) && str.length() > 255) {
str = str.substring(0, 256);
str = str.substring(0, 255);
}else if(str.length() > 65535) {
str = str.substring(0, 65536);
str = str.substring(0, 65535);
}
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
connectionClosed = true;
ByteBuf buf = Unpooled.buffer();
ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(HandshakePacketTypes.PROTOCOL_SERVER_ERROR);
buf.writeByte(code);
byte[] msg = str.getBytes(StandardCharsets.UTF_8);

View File

@ -0,0 +1,295 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import org.apache.commons.lang3.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.EaglerBackendRPCProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.EaglerBackendRPCHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.EaglerBackendRPCPacket;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.WrongRPCPacketException;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.client.CPacketRPCEnabled;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.client.CPacketRPCSubscribeEvents;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEnabledFailure;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEnabledSuccess;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventToggledVoice;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventWebViewOpenClose;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayInputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayOutputStream;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.Server;
/**
* 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 BackendRPCSessionHandler {
public static BackendRPCSessionHandler createForPlayer(EaglerInitialHandler eaglerHandler) {
return new BackendRPCSessionHandler(eaglerHandler);
}
protected final EaglerInitialHandler eaglerHandler;
private Server currentServer = null;
private EaglerBackendRPCProtocol currentProtocol = null;
private EaglerBackendRPCHandler currentHandler = null;
private int subscribedEvents = 0;
private final AtomicInteger currentVoiceState = new AtomicInteger(SPacketRPCEventToggledVoice.VOICE_STATE_SERVER_DISABLE);
private final ReentrantLock inputStreamLock = new ReentrantLock();
private final ReentrantLock outputStreamLock = new ReentrantLock();
private final ReusableByteArrayInputStream reusableInputStream = new ReusableByteArrayInputStream();
private final ReusableByteArrayOutputStream reusableOutputStream = new ReusableByteArrayOutputStream();
private final DataInputStream dataInputStream = new DataInputStream(reusableInputStream);
private final DataOutputStream dataOutputStream = new DataOutputStream(reusableOutputStream);
private BackendRPCSessionHandler(EaglerInitialHandler eaglerHandler) {
this.eaglerHandler = eaglerHandler;
}
public void handleRPCPacket(Server server, byte[] data) {
synchronized(this) {
if(currentServer != null) {
if(currentServer != server) {
return;
}
}else {
handleCreateContext(server, data);
return;
}
}
EaglerBackendRPCPacket packet;
try {
packet = decodeRPCPacket(currentProtocol, data);
} catch (IOException e) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + eaglerHandler.getSocketAddress()
+ "]: Recieved invalid backend RPC protocol packet for user \"" + eaglerHandler.getName() + "\"", e);
return;
}
packet.handlePacket(currentHandler);
}
protected EaglerBackendRPCPacket decodeRPCPacket(EaglerBackendRPCProtocol protocol, byte[] data) throws IOException {
EaglerBackendRPCPacket ret;
if(inputStreamLock.tryLock()) {
try {
reusableInputStream.feedBuffer(data);
ret = protocol.readPacket(dataInputStream, EaglerBackendRPCProtocol.CLIENT_TO_SERVER);
}finally {
inputStreamLock.unlock();
}
}else {
ReusableByteArrayInputStream bai = new ReusableByteArrayInputStream();
bai.feedBuffer(data);
ret = protocol.readPacket(new DataInputStream(bai), EaglerBackendRPCProtocol.CLIENT_TO_SERVER);
}
return ret;
}
public void sendRPCPacket(EaglerBackendRPCPacket packet) {
if(currentServer != null) {
sendRPCPacket(currentProtocol, currentServer, packet);
}else {
EaglerXBungee.logger()
.warning("[" + eaglerHandler.getSocketAddress()
+ "]: Failed to write backend RPC protocol version for user \"" + eaglerHandler.getName()
+ "\", the RPC connection is not initialized!");
}
}
protected void sendRPCPacket(EaglerBackendRPCProtocol protocol, Server server, EaglerBackendRPCPacket packet) {
byte[] ret;
int len = packet.length() + 1;
if(outputStreamLock.tryLock()) {
try {
reusableOutputStream.feedBuffer(new byte[len > 0 ? len : 64]);
try {
protocol.writePacket(dataOutputStream, EaglerBackendRPCProtocol.SERVER_TO_CLIENT, packet);
}catch(IOException ex) {
throw new IllegalStateException("Failed to serialize packet: " + packet.getClass().getSimpleName(), ex);
}
ret = reusableOutputStream.returnBuffer();
}finally {
outputStreamLock.unlock();
}
}else {
ReusableByteArrayOutputStream bao = new ReusableByteArrayOutputStream();
bao.feedBuffer(new byte[len > 0 ? len : 64]);
try {
protocol.writePacket(new DataOutputStream(bao), EaglerBackendRPCProtocol.SERVER_TO_CLIENT, packet);
}catch(IOException ex) {
throw new IllegalStateException("Failed to serialize packet: " + packet.getClass().getSimpleName(), ex);
}
ret = bao.returnBuffer();
}
if(len > 0 && len != ret.length) {
EaglerXBungee.logger()
.warning("[" + eaglerHandler.getSocketAddress() + "]: Backend RPC packet type "
+ packet.getClass().getSimpleName() + " was the wrong length for user \""
+ eaglerHandler.getName() + "\" after serialization: " + ret.length + " != " + len);
}
server.sendData(EaglerBackendRPCProtocol.CHANNEL_NAME, ret);
}
public void handleConnectionLost(ServerInfo server) {
if(currentServer != null) {
handleDestroyContext();
}
}
private void handleDestroyContext() {
currentServer = null;
currentProtocol = null;
currentHandler = null;
subscribedEvents = 0;
}
private void handleCreateContext(Server server, byte[] data) {
EaglerBackendRPCPacket packet;
try {
packet = decodeRPCPacket(EaglerBackendRPCProtocol.INIT, data);
} catch (IOException e) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + eaglerHandler.getSocketAddress()
+ "]: Recieved invalid backend RPC protocol handshake for user \"" + eaglerHandler.getName() + "\"", e);
return;
}
if(!(packet instanceof CPacketRPCEnabled)) {
throw new WrongRPCPacketException();
}
if(!ArrayUtils.contains(((CPacketRPCEnabled)packet).supportedProtocols, EaglerBackendRPCProtocol.V1.vers)) {
EaglerXBungee.logger().severe("[" + eaglerHandler.getSocketAddress()
+ "]: Unsupported backend RPC protocol version for user \"" + eaglerHandler.getName() + "\"");
sendRPCPacket(EaglerBackendRPCProtocol.INIT, server, new SPacketRPCEnabledFailure(SPacketRPCEnabledFailure.FAILURE_CODE_OUTDATED_SERVER));
return;
}
sendRPCPacket(EaglerBackendRPCProtocol.INIT, server, new SPacketRPCEnabledSuccess(EaglerBackendRPCProtocol.V1.vers, eaglerHandler.getEaglerProtocolHandshake()));
currentServer = server;
currentProtocol = EaglerBackendRPCProtocol.V1;
currentHandler = new ServerV1RPCProtocolHandler(this, server, eaglerHandler);
}
public static void handlePacketOnVanilla(Server server, UserConnection player, byte[] data) {
EaglerBackendRPCPacket packet;
try {
packet = EaglerBackendRPCProtocol.INIT.readPacket(new DataInputStream(new ByteArrayInputStream(data)), EaglerBackendRPCProtocol.CLIENT_TO_SERVER);
} catch (IOException e) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + player.getSocketAddress()
+ "]: Recieved invalid backend RPC protocol handshake for user \"" + player.getName() + "\"", e);
EaglerXBungee.logger().severe("(Note: this player is not using Eaglercraft!)");
return;
}
if(!(packet instanceof CPacketRPCEnabled)) {
throw new WrongRPCPacketException();
}
if(!ArrayUtils.contains(((CPacketRPCEnabled)packet).supportedProtocols, EaglerBackendRPCProtocol.V1.vers)) {
EaglerXBungee.logger().severe("[" + player.getSocketAddress()
+ "]: Unsupported backend RPC protocol version for user \"" + player.getName() + "\"");
ByteArrayOutputStream bao = new ByteArrayOutputStream();
try {
EaglerBackendRPCProtocol.INIT.writePacket(new DataOutputStream(bao),
EaglerBackendRPCProtocol.SERVER_TO_CLIENT,
new SPacketRPCEnabledFailure(SPacketRPCEnabledFailure.FAILURE_CODE_OUTDATED_SERVER));
}catch(IOException ex) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + player.getSocketAddress()
+ "]: Failed to write backend RPC protocol version for user \"" + player.getName() + "\"", ex);
EaglerXBungee.logger().severe("(Note: this player is not using Eaglercraft!)");
return;
}
server.sendData(EaglerBackendRPCProtocol.CHANNEL_NAME, bao.toByteArray());
return;
}
EaglerXBungee.logger().warning("[" + player.getSocketAddress()
+ "]: Tried to open backend RPC protocol connection for non-eagler player \"" + player.getName() + "\"");
ByteArrayOutputStream bao = new ByteArrayOutputStream();
try {
EaglerBackendRPCProtocol.INIT.writePacket(new DataOutputStream(bao),
EaglerBackendRPCProtocol.SERVER_TO_CLIENT,
new SPacketRPCEnabledFailure(SPacketRPCEnabledFailure.FAILURE_CODE_NOT_EAGLER_PLAYER));
}catch(IOException ex) {
EaglerXBungee.logger().log(Level.SEVERE, "[" + player.getSocketAddress()
+ "]: Failed to write backend RPC protocol version for user \"" + player.getName() + "\"", ex);
return;
}
server.sendData(EaglerBackendRPCProtocol.CHANNEL_NAME, bao.toByteArray());
}
public void setSubscribedEvents(int eventsToEnable) {
int oldSubscribedEvents = subscribedEvents;
subscribedEvents = eventsToEnable;
if ((oldSubscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_TOGGLE_VOICE) == 0
&& (eventsToEnable & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_TOGGLE_VOICE) != 0) {
currentVoiceState.set(SPacketRPCEventToggledVoice.VOICE_STATE_SERVER_DISABLE);
VoiceService svc = EaglerXBungee.getEagler().getVoiceService();
if(svc != null && eaglerHandler.getEaglerListenerConfig().getEnableVoiceChat()) {
EnumVoiceState state = svc.getPlayerVoiceState(eaglerHandler.getUniqueId(), currentServer.getInfo());
if(state == EnumVoiceState.DISABLED) {
handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_DISABLED);
}else if(state == EnumVoiceState.ENABLED) {
handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_ENABLED);
}
}
}
if ((oldSubscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_OPEN_CLOSE) == 0
&& (eventsToEnable & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_OPEN_CLOSE) != 0) {
if(eaglerHandler.webViewMessageChannelOpen.get()) {
sendRPCPacket(new SPacketRPCEventWebViewOpenClose(true, eaglerHandler.webViewMessageChannelName));
}
}
if ((eventsToEnable & ~(CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_OPEN_CLOSE
| CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_MESSAGE
| CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_TOGGLE_VOICE)) != 0) {
EaglerXBungee.logger()
.severe("[" + eaglerHandler.getSocketAddress() + "]: Unsupported events were subscribed to for \""
+ eaglerHandler.getName() + "\" via backend RPC protocol, bitfield: " + subscribedEvents);
}
}
public boolean isSubscribed(EnumSubscribedEvent eventType) {
switch(eventType) {
case WEBVIEW_OPEN_CLOSE:
return (subscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_OPEN_CLOSE) != 0;
case WEBVIEW_MESSAGE:
return (subscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_WEBVIEW_MESSAGE) != 0;
case TOGGLE_VOICE:
return (subscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_TOGGLE_VOICE) != 0;
default:
return false;
}
}
public void handleDisabled() {
handleDestroyContext();
}
public void handleVoiceStateTransition(int voiceState) {
if((subscribedEvents & CPacketRPCSubscribeEvents.SUBSCRIBE_EVENT_TOGGLE_VOICE) != 0) {
int oldState = currentVoiceState.getAndSet(voiceState);
if(oldState != voiceState) {
sendRPCPacket(new SPacketRPCEventToggledVoice(oldState, voiceState));
}
}
}
}

View File

@ -0,0 +1,22 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol;
/**
* 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 enum EnumSubscribedEvent {
WEBVIEW_OPEN_CLOSE,
WEBVIEW_MESSAGE,
TOGGLE_VOICE;
}

View File

@ -0,0 +1,435 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.RandomAccess;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.EaglerBackendRPCHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.client.*;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.*;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.util.PacketImageData;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumWebViewState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerPauseMenuConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketCustomizePauseMenuV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeHideV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeShowV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifIconsRegisterV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifIconsReleaseV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.md_5.bungee.api.connection.Server;
/**
* 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 ServerV1RPCProtocolHandler implements EaglerBackendRPCHandler {
protected final BackendRPCSessionHandler sessionHandler;
protected final Server server;
protected final EaglerInitialHandler eaglerHandler;
public ServerV1RPCProtocolHandler(BackendRPCSessionHandler sessionHandler, Server server, EaglerInitialHandler eaglerHandler) {
this.sessionHandler = sessionHandler;
this.server = server;
this.eaglerHandler = eaglerHandler;
}
public void handleClient(CPacketRPCRequestPlayerInfo packet) {
switch(packet.requestType) {
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_REAL_UUID: {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeUUID(packet.requestID, eaglerHandler.getUniqueId()));
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_REAL_IP: {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, ((InetSocketAddress)eaglerHandler.getSocketAddress()).getAddress().getHostAddress()));
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_ORIGIN: {
String origin = eaglerHandler.getOrigin();
if(origin != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, origin));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_USER_AGENT: {
String userAgent = eaglerHandler.getUserAgent();
if(userAgent != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, userAgent));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_SKIN_DATA: {
SkinPacketVersionCache skinData = EaglerXBungee.getEagler().getSkinService().getSkin(eaglerHandler.getUniqueId());
if(skinData != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeBytes(packet.requestID, skinData.getV3HandshakeData()));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CAPE_DATA: {
byte[] capeData = EaglerXBungee.getEagler().getCapeService().getCapeHandshakeData(eaglerHandler.getUniqueId());
if(capeData != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeBytes(packet.requestID, capeData));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_COOKIE: {
boolean cookieEnabled = eaglerHandler.getCookieAllowed();
byte[] cookieData = cookieEnabled ? eaglerHandler.getCookieData() : null;
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeCookie(packet.requestID, cookieEnabled, cookieData));
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_BRAND_STR: {
String clientBrandStr = eaglerHandler.getEaglerBrandString();
if(clientBrandStr != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, clientBrandStr));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_VERSION_STR: {
String clientVersionStr = eaglerHandler.getEaglerVersionString();
if(clientVersionStr != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, clientVersionStr));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_BRAND_VERSION_STR: {
String clientBrandStr = eaglerHandler.getEaglerBrandString();
String clientVersionStr = eaglerHandler.getEaglerVersionString();
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeString(packet.requestID, "" + clientBrandStr + " " + clientVersionStr));
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_BRAND_UUID: {
UUID brandUUID = eaglerHandler.getClientBrandUUID();
if(brandUUID != null) {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeUUID(packet.requestID, brandUUID));
}else {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeNull(packet.requestID));
}
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_VOICE_STATUS: {
int voiceState;
VoiceService svc = EaglerXBungee.getEagler().getVoiceService();
if(svc != null && eaglerHandler.getEaglerListenerConfig().getEnableVoiceChat()) {
EnumVoiceState enumVoiceState = svc.getPlayerVoiceState(eaglerHandler.getUniqueId(), server.getInfo());
switch(enumVoiceState) {
case SERVER_DISABLE:
default:
voiceState = SPacketRPCResponseTypeVoiceStatus.VOICE_STATE_SERVER_DISABLE;
break;
case DISABLED:
voiceState = SPacketRPCResponseTypeVoiceStatus.VOICE_STATE_DISABLED;
break;
case ENABLED:
voiceState = SPacketRPCResponseTypeVoiceStatus.VOICE_STATE_ENABLED;
break;
}
}else {
voiceState = SPacketRPCResponseTypeVoiceStatus.VOICE_STATE_SERVER_DISABLE;
}
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeVoiceStatus(packet.requestID, voiceState));
}
break;
case CPacketRPCRequestPlayerInfo.REQUEST_PLAYER_CLIENT_WEBVIEW_STATUS: {
EnumWebViewState enumWebViewState = eaglerHandler.getWebViewState();
int webViewStatus;
String webViewChannel;
switch(enumWebViewState) {
case NOT_SUPPORTED:
default:
webViewStatus = SPacketRPCResponseTypeWebViewStatus.WEBVIEW_STATE_NOT_SUPPORTED;
webViewChannel = null;
break;
case SERVER_DISABLE:
webViewStatus = SPacketRPCResponseTypeWebViewStatus.WEBVIEW_STATE_SERVER_DISABLE;
webViewChannel = null;
break;
case CHANNEL_CLOSED:
webViewStatus = SPacketRPCResponseTypeWebViewStatus.WEBVIEW_STATE_CHANNEL_CLOSED;
webViewChannel = null;
break;
case CHANNEL_OPEN:
webViewStatus = SPacketRPCResponseTypeWebViewStatus.WEBVIEW_STATE_CHANNEL_OPEN;
webViewChannel = eaglerHandler.getWebViewMessageChannelName();
break;
}
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeWebViewStatus(packet.requestID, webViewStatus, webViewChannel));
}
break;
default: {
sessionHandler.sendRPCPacket(new SPacketRPCResponseTypeError(packet.requestID, "Unknown request type: " + packet.requestType));
}
break;
}
}
public void handleClient(CPacketRPCSubscribeEvents packet) {
sessionHandler.setSubscribedEvents(packet.eventsToEnable);
}
public void handleClient(CPacketRPCSetPlayerSkin packet) {
try {
byte[] bs = packet.skinPacket;
if(bs.length < 5) {
throw new IOException();
}
if(bs[0] == (byte)1) {
if(bs.length != 5) {
throw new IOException();
}
EaglerXBungeeAPIHelper.changePlayerSkinPreset(EaglerXBungeeAPIHelper.getPlayer(eaglerHandler),
(bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF), packet.notifyOthers);
}else if(bs[0] == (byte)2) {
if(bs.length < 2) {
throw new IOException();
}
byte[] cust = new byte[bs.length - 2];
System.arraycopy(bs, 2, cust, 0, cust.length);
EaglerXBungeeAPIHelper.changePlayerSkinCustom(EaglerXBungeeAPIHelper.getPlayer(eaglerHandler),
bs[1] & 0xFF, cust, packet.notifyOthers);
}else {
throw new IOException();
}
}catch(IOException ex) {
EaglerXBungee.logger().severe("[" + eaglerHandler.getSocketAddress() + "]: Invalid CPacketRPCSetPlayerSkin packet recieved for player \""
+ eaglerHandler.getName() + "\" from backend RPC protocol!");
return;
}
}
public void handleClient(CPacketRPCSetPlayerCape packet) {
try {
byte[] bs = packet.capePacket;
if(bs.length < 5) {
throw new IOException();
}
if(bs[0] == (byte)1) {
if(bs.length != 5) {
throw new IOException();
}
EaglerXBungeeAPIHelper.changePlayerCapePreset(EaglerXBungeeAPIHelper.getPlayer(eaglerHandler),
(bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF), packet.notifyOthers);
}else if(bs[0] == (byte)2) {
if(bs.length != 1174) {
throw new IOException();
}
byte[] cust = new byte[bs.length - 1];
System.arraycopy(bs, 1, cust, 0, cust.length);
EaglerXBungeeAPIHelper.changePlayerCapeCustom(EaglerXBungeeAPIHelper.getPlayer(eaglerHandler),
cust, true, packet.notifyOthers);
}else {
throw new IOException();
}
}catch(IOException ex) {
EaglerXBungee.logger().severe("[" + eaglerHandler.getSocketAddress() + "]: Invalid CPacketRPCSetPlayerCape packet recieved for player \""
+ eaglerHandler.getName() + "\" from backend RPC protocol!");
return;
}
}
public void handleClient(CPacketRPCSetPlayerCookie packet) {
eaglerHandler.setCookieData(packet.cookieData, packet.expires, packet.revokeQuerySupported, packet.saveToDisk);
}
public void handleClient(CPacketRPCSetPlayerFNAWEn packet) {
EaglerXBungeeAPIHelper.setEnableForceFNAWSkins(eaglerHandler, packet.enable, packet.force);
}
public void handleClient(CPacketRPCRedirectPlayer packet) {
eaglerHandler.redirectPlayerToWebSocket(packet.redirectURI);
}
public void handleClient(CPacketRPCResetPlayerMulti packet) {
EaglerXBungeeAPIHelper.resetPlayerMulti(EaglerXBungeeAPIHelper.getPlayer(eaglerHandler), packet.resetSkin,
packet.resetCape, packet.resetFNAWForce, packet.notifyOtherPlayers);
}
public void handleClient(CPacketRPCSendWebViewMessage packet) {
eaglerHandler.sendWebViewMessage(packet.messageType, packet.messageContent);
}
public void handleClient(CPacketRPCSetPauseMenuCustom packet) {
if(eaglerHandler.getEaglerProtocol().ver >= 4) {
EaglerPauseMenuConfig defaultConf = EaglerXBungee.getEagler().getConfig().getPauseMenuConf();
SPacketCustomizePauseMenuV4EAG defaultPacket = defaultConf.getPacket();
int serverInfoMode = packet.serverInfoMode;
String serverInfoButtonText;
String serverInfoURL;
byte[] serverInfoHash;
int serverInfoEmbedPerms;
String serverInfoEmbedTitle;
if(serverInfoMode == CPacketRPCSetPauseMenuCustom.SERVER_INFO_MODE_INHERIT_DEFAULT) {
serverInfoMode = defaultPacket.serverInfoMode;
serverInfoButtonText = defaultPacket.serverInfoButtonText;
serverInfoURL = defaultPacket.serverInfoURL;
serverInfoHash = defaultPacket.serverInfoHash;
serverInfoEmbedPerms = defaultPacket.serverInfoEmbedPerms;
serverInfoEmbedTitle = defaultPacket.serverInfoEmbedTitle;
}else {
serverInfoButtonText = packet.serverInfoButtonText;
serverInfoURL = packet.serverInfoURL;
serverInfoHash = packet.serverInfoHash;
serverInfoEmbedPerms = packet.serverInfoEmbedPerms;
serverInfoEmbedTitle = packet.serverInfoEmbedTitle;
}
int discordButtonMode = packet.discordButtonMode;
String discordButtonText;
String discordInviteURL;
if(discordButtonMode == CPacketRPCSetPauseMenuCustom.DISCORD_MODE_INHERIT_DEFAULT) {
discordButtonMode = defaultPacket.discordButtonMode;
discordButtonText = defaultPacket.discordButtonText;
discordInviteURL = defaultPacket.discordInviteURL;
}else {
discordButtonText = packet.discordButtonText;
discordInviteURL = packet.discordInviteURL;
}
Map<String, Integer> imageMappings = packet.imageMappings;
List<PacketImageData> imageData = packet.imageData;
List<net.lax1dude.eaglercraft.v1_8.socket.protocol.util.PacketImageData> imageDataConv = imageData != null
? new ArrayList<>(imageData.size()) : null;
if(imageDataConv != null) {
for(int i = 0, l = imageData.size(); i < l; ++i) {
PacketImageData etr = imageData.get(i);
imageDataConv.add(new net.lax1dude.eaglercraft.v1_8.socket.protocol.util.PacketImageData
(etr.width, etr.height, etr.rgba));
}
}
eaglerHandler.sendEaglerMessage(new SPacketCustomizePauseMenuV4EAG(serverInfoMode, serverInfoButtonText,
serverInfoURL, serverInfoHash, serverInfoEmbedPerms, serverInfoEmbedTitle, discordButtonMode,
discordButtonText, discordInviteURL, imageMappings, imageDataConv));
}else {
EaglerXBungee.logger().warning("[" + eaglerHandler.getSocketAddress()
+ "]: Recieved packet CPacketRPCSetPauseMenuCustom for player \"" + eaglerHandler.getName()
+ "\" from backend RPC protocol, but their client does not support pause menu customization!");
}
}
public void handleClient(CPacketRPCNotifIconRegister packet) {
if(eaglerHandler.notificationSupported()) {
List<SPacketNotifIconsRegisterV4EAG.CreateIcon> createIconsConv = new ArrayList<>(packet.notifIcons.size());
for(Entry<UUID,PacketImageData> etr : packet.notifIcons.entrySet()) {
UUID uuid = etr.getKey();
PacketImageData imgData = etr.getValue();
createIconsConv.add(new SPacketNotifIconsRegisterV4EAG.CreateIcon(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits(), new net.lax1dude.eaglercraft.v1_8.socket.protocol.util.PacketImageData
(imgData.width, imgData.height, imgData.rgba)));
}
eaglerHandler.sendEaglerMessage(new SPacketNotifIconsRegisterV4EAG(createIconsConv));
}else {
EaglerXBungee.logger().warning("[" + eaglerHandler.getSocketAddress()
+ "]: Recieved packet CPacketRPCNotifIconRegister for player \"" + eaglerHandler.getName()
+ "\" from backend RPC protocol, but their client does not support notifications!");
}
}
public void handleClient(CPacketRPCNotifIconRelease packet) {
if(eaglerHandler.notificationSupported()) {
List<SPacketNotifIconsReleaseV4EAG.DestroyIcon> destroyIconsConv = new ArrayList<>(packet.iconsToRelease.size());
if(packet.iconsToRelease instanceof RandomAccess) {
List<UUID> lst = (List<UUID>)packet.iconsToRelease;
for(int i = 0, l = lst.size(); i < l; ++i) {
UUID uuid = lst.get(i);
destroyIconsConv.add(new SPacketNotifIconsReleaseV4EAG.DestroyIcon(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits()));
}
}else {
for(UUID uuid : packet.iconsToRelease) {
destroyIconsConv.add(new SPacketNotifIconsReleaseV4EAG.DestroyIcon(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits()));
}
}
eaglerHandler.sendEaglerMessage(new SPacketNotifIconsReleaseV4EAG(destroyIconsConv));
}else {
EaglerXBungee.logger().warning("[" + eaglerHandler.getSocketAddress()
+ "]: Recieved packet CPacketRPCNotifIconRelease for player \"" + eaglerHandler.getName()
+ "\" from backend RPC protocol, but their client does not support notifications!");
}
}
public void handleClient(CPacketRPCNotifBadgeShow packet) {
if(eaglerHandler.notificationSupported()) {
SPacketNotifBadgeShowV4EAG.EnumBadgePriority translatedEnum;
switch(packet.priority) {
case LOW:
default:
translatedEnum = SPacketNotifBadgeShowV4EAG.EnumBadgePriority.LOW;
break;
case NORMAL:
translatedEnum = SPacketNotifBadgeShowV4EAG.EnumBadgePriority.NORMAL;
break;
case HIGHER:
translatedEnum = SPacketNotifBadgeShowV4EAG.EnumBadgePriority.HIGHER;
break;
case HIGHEST:
translatedEnum = SPacketNotifBadgeShowV4EAG.EnumBadgePriority.HIGHEST;
break;
}
eaglerHandler.sendEaglerMessage(new SPacketNotifBadgeShowV4EAG(packet.badgeUUID.getMostSignificantBits(),
packet.badgeUUID.getLeastSignificantBits(), packet.bodyComponent, packet.titleComponent,
packet.sourceComponent, packet.originalTimestampSec, packet.silent, translatedEnum,
(packet.mainIconUUID != null ? packet.mainIconUUID.getMostSignificantBits() : 0l),
(packet.mainIconUUID != null ? packet.mainIconUUID.getLeastSignificantBits() : 0l),
(packet.titleIconUUID != null ? packet.titleIconUUID.getMostSignificantBits() : 0l),
(packet.titleIconUUID != null ? packet.titleIconUUID.getLeastSignificantBits() : 0l),
packet.hideAfterSec, packet.expireAfterSec, packet.backgroundColor, packet.bodyTxtColor,
packet.titleTxtColor, packet.sourceTxtColor));
}else {
EaglerXBungee.logger().warning("[" + eaglerHandler.getSocketAddress()
+ "]: Recieved packet CPacketRPCNotifBadgeShow for player \"" + eaglerHandler.getName()
+ "\" from backend RPC protocol, but their client does not support notifications!");
}
}
public void handleClient(CPacketRPCNotifBadgeHide packet) {
if(eaglerHandler.notificationSupported()) {
eaglerHandler.sendEaglerMessage(new SPacketNotifBadgeHideV4EAG(packet.badgeUUID.getMostSignificantBits(),
packet.badgeUUID.getLeastSignificantBits()));
}else {
EaglerXBungee.logger().warning("[" + eaglerHandler.getSocketAddress()
+ "]: Recieved packet CPacketRPCNotifBadgeHide for player \"" + eaglerHandler.getName()
+ "\" from backend RPC protocol, but their client does not support notifications!");
}
}
public void handleClient(CPacketRPCDisabled packet) {
sessionHandler.handleDisabled();
}
public void handleClient(CPacketRPCSendRawMessage packet) {
eaglerHandler.getEaglerMessageController().getUserConnection().sendData(packet.messageChannel, packet.messageData);
}
}

View File

@ -1,254 +0,0 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TObjectIntHashMap;
import net.md_5.bungee.protocol.BadPacketException;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.*;
import java.util.function.Supplier;
/**
* The original net.md_5.bungee.protocol.Protocol is inaccessible due to java
* security rules
*/
public enum EaglerBungeeProtocol {
// Undef
HANDSHAKE {
{
TO_SERVER.registerPacket(Handshake.class, Handshake::new, map(ProtocolConstants.MINECRAFT_1_8, 0x00));
}
},
// 0
GAME {
{
TO_CLIENT.registerPacket(KeepAlive.class, KeepAlive::new, map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_CLIENT.registerPacket(Login.class, Login::new, map(ProtocolConstants.MINECRAFT_1_8, 0x01));
TO_CLIENT.registerPacket(Chat.class, Chat::new, map(ProtocolConstants.MINECRAFT_1_8, 0x02));
TO_CLIENT.registerPacket(Respawn.class, Respawn::new, map(ProtocolConstants.MINECRAFT_1_8, 0x07));
TO_CLIENT.registerPacket(PlayerListItem.class, // PlayerInfo
PlayerListItem::new, map(ProtocolConstants.MINECRAFT_1_8, 0x38));
TO_CLIENT.registerPacket(TabCompleteResponse.class, TabCompleteResponse::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x3A));
TO_CLIENT.registerPacket(ScoreboardObjective.class, ScoreboardObjective::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x3B));
TO_CLIENT.registerPacket(ScoreboardScore.class, ScoreboardScore::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x3C));
TO_CLIENT.registerPacket(ScoreboardDisplay.class, ScoreboardDisplay::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x3D));
TO_CLIENT.registerPacket(Team.class, Team::new, map(ProtocolConstants.MINECRAFT_1_8, 0x3E));
TO_CLIENT.registerPacket(PluginMessage.class, PluginMessage::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x3F));
TO_CLIENT.registerPacket(Kick.class, Kick::new, map(ProtocolConstants.MINECRAFT_1_8, 0x40));
TO_CLIENT.registerPacket(Title.class, Title::new, map(ProtocolConstants.MINECRAFT_1_8, 0x45));
TO_CLIENT.registerPacket(PlayerListHeaderFooter.class, PlayerListHeaderFooter::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x47));
TO_CLIENT.registerPacket(EntityStatus.class, EntityStatus::new, map(ProtocolConstants.MINECRAFT_1_8, 0x1A));
TO_SERVER.registerPacket(KeepAlive.class, KeepAlive::new, map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_SERVER.registerPacket(Chat.class, Chat::new, map(ProtocolConstants.MINECRAFT_1_8, 0x01));
TO_SERVER.registerPacket(TabCompleteRequest.class, TabCompleteRequest::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x14));
TO_SERVER.registerPacket(ClientSettings.class, ClientSettings::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x15));
TO_SERVER.registerPacket(PluginMessage.class, PluginMessage::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x17));
}
},
// 1
STATUS {
{
TO_CLIENT.registerPacket(StatusResponse.class, StatusResponse::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_CLIENT.registerPacket(PingPacket.class, PingPacket::new, map(ProtocolConstants.MINECRAFT_1_8, 0x01));
TO_SERVER.registerPacket(StatusRequest.class, StatusRequest::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_SERVER.registerPacket(PingPacket.class, PingPacket::new, map(ProtocolConstants.MINECRAFT_1_8, 0x01));
}
},
// 2
LOGIN {
{
TO_CLIENT.registerPacket(Kick.class, Kick::new, map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_CLIENT.registerPacket(EncryptionRequest.class, EncryptionRequest::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x01));
TO_CLIENT.registerPacket(LoginSuccess.class, LoginSuccess::new, map(ProtocolConstants.MINECRAFT_1_8, 0x02));
TO_CLIENT.registerPacket(SetCompression.class, SetCompression::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x03));
TO_SERVER.registerPacket(LoginRequest.class, LoginRequest::new, map(ProtocolConstants.MINECRAFT_1_8, 0x00));
TO_SERVER.registerPacket(EncryptionResponse.class, EncryptionResponse::new,
map(ProtocolConstants.MINECRAFT_1_8, 0x01));
}
},
// 3
CONFIGURATION {
};
/* ======================================================================== */
public static final int MAX_PACKET_ID = 0xFF;
/* ======================================================================== */
public final DirectionData TO_SERVER = new DirectionData(this, ProtocolConstants.Direction.TO_SERVER);
public final DirectionData TO_CLIENT = new DirectionData(this, ProtocolConstants.Direction.TO_CLIENT);
public static void main(String[] args) {
for (int version : ProtocolConstants.SUPPORTED_VERSION_IDS) {
dump(version);
}
}
private static void dump(int version) {
for (EaglerBungeeProtocol protocol : EaglerBungeeProtocol.values()) {
dump(version, protocol);
}
}
private static void dump(int version, EaglerBungeeProtocol protocol) {
dump(version, protocol.TO_CLIENT);
dump(version, protocol.TO_SERVER);
}
private static void dump(int version, DirectionData data) {
for (int id = 0; id < MAX_PACKET_ID; id++) {
DefinedPacket packet = data.createPacket(id, version);
if (packet != null) {
System.out.println(version + " " + data.protocolPhase + " " + data.direction + " " + id + " "
+ packet.getClass().getSimpleName());
}
}
}
private static class ProtocolData {
private final int protocolVersion;
private final TObjectIntMap<Class<? extends DefinedPacket>> packetMap = new TObjectIntHashMap<>(MAX_PACKET_ID);
@SuppressWarnings("unchecked")
private final Supplier<? extends DefinedPacket>[] packetConstructors = new Supplier[MAX_PACKET_ID];
private ProtocolData(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
}
private static class ProtocolMapping {
private final int protocolVersion;
private final int packetID;
private ProtocolMapping(int protocolVersion, int packetID) {
this.protocolVersion = protocolVersion;
this.packetID = packetID;
}
}
// Helper method
private static ProtocolMapping map(int protocol, int id) {
return new ProtocolMapping(protocol, id);
}
public static final class DirectionData {
private final TIntObjectMap<ProtocolData> protocols = new TIntObjectHashMap<>();
//
private final EaglerBungeeProtocol protocolPhase;
private final ProtocolConstants.Direction direction;
public DirectionData(EaglerBungeeProtocol protocolPhase, ProtocolConstants.Direction direction) {
this.protocolPhase = protocolPhase;
this.direction = direction;
for (int protocol : ProtocolConstants.SUPPORTED_VERSION_IDS) {
protocols.put(protocol, new ProtocolData(protocol));
}
}
private ProtocolData getProtocolData(int version) {
ProtocolData protocol = protocols.get(version);
if (protocol == null && (protocolPhase != EaglerBungeeProtocol.GAME)) {
protocol = Iterables.getFirst(protocols.valueCollection(), null);
}
return protocol;
}
public final DefinedPacket createPacket(int id, int version) {
ProtocolData protocolData = getProtocolData(version);
if (protocolData == null) {
throw new BadPacketException("Unsupported protocol version " + version);
}
if (id > MAX_PACKET_ID || id < 0) {
throw new BadPacketException("Packet with id " + id + " outside of range");
}
Supplier<? extends DefinedPacket> constructor = protocolData.packetConstructors[id];
return (constructor == null) ? null : constructor.get();
}
private void registerPacket(Class<? extends DefinedPacket> packetClass,
Supplier<? extends DefinedPacket> constructor, ProtocolMapping... mappings) {
int mappingIndex = 0;
ProtocolMapping mapping = mappings[mappingIndex];
for (int protocol : ProtocolConstants.SUPPORTED_VERSION_IDS) {
if (protocol < mapping.protocolVersion) {
// This is a new packet, skip it till we reach the next protocol
continue;
}
if (mapping.protocolVersion < protocol && mappingIndex + 1 < mappings.length) {
// Mapping is non current, but the next one may be ok
ProtocolMapping nextMapping = mappings[mappingIndex + 1];
if (nextMapping.protocolVersion == protocol) {
Preconditions.checkState(nextMapping.packetID != mapping.packetID,
"Duplicate packet mapping (%s, %s)", mapping.protocolVersion,
nextMapping.protocolVersion);
mapping = nextMapping;
mappingIndex++;
}
}
if (mapping.packetID < 0) {
break;
}
ProtocolData data = protocols.get(protocol);
data.packetMap.put(packetClass, mapping.packetID);
data.packetConstructors[mapping.packetID] = constructor;
}
}
public boolean hasPacket(Class<? extends DefinedPacket> packet, int version) {
ProtocolData protocolData = getProtocolData(version);
if (protocolData == null) {
throw new BadPacketException("Unsupported protocol version");
}
return protocolData.packetMap.containsKey(packet);
}
final int getId(Class<? extends DefinedPacket> packet, int version) {
ProtocolData protocolData = getProtocolData(version);
if (protocolData == null) {
throw new BadPacketException("Unsupported protocol version");
}
Preconditions.checkArgument(protocolData.packetMap.containsKey(packet),
"Cannot get ID for packet %s in phase %s with direction %s", packet, protocolPhase, direction);
return protocolData.packetMap.get(packet);
}
}
}

View File

@ -0,0 +1,291 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.concurrent.ScheduledFuture;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePacketOutputBuffer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageConstants;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayInputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayOutputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SimpleInputBufferImpl;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SimpleOutputBufferImpl;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.connection.PendingConnection;
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 GameProtocolMessageController {
public final GamePluginMessageProtocol protocol;
public final GameMessageHandler handler;
private final ReusableByteArrayInputStream byteInputStreamSingleton = new ReusableByteArrayInputStream();
private final ReusableByteArrayOutputStream byteOutputStreamSingleton = new ReusableByteArrayOutputStream();
private final SimpleInputBufferImpl inputStreamSingleton = new SimpleInputBufferImpl(byteInputStreamSingleton);
private final SimpleOutputBufferImpl outputStreamSingleton = new SimpleOutputBufferImpl(byteOutputStreamSingleton);
private final ReentrantLock inputStreamLock = new ReentrantLock();
private final ReentrantLock outputStreamLock = new ReentrantLock();
private final UserConnection owner;
private int defagSendDelay;
private final List<byte[]> sendQueueV4 = new LinkedList<>();
private volatile int sendQueueByteLengthV4 = 0;
private volatile Callable<Void> futureSendCallableV4 = null;
private volatile ScheduledFuture<Void> futureSendTaskV4 = null;
public GameProtocolMessageController(UserConnection owner, GamePluginMessageProtocol protocol,
GameMessageHandler handler, int defagSendDelay) {
this.owner = owner;
this.protocol = protocol;
this.handler = handler;
this.defagSendDelay = defagSendDelay;
}
public boolean handlePacket(String channel, byte[] data) throws IOException {
GameMessagePacket pkt;
if(inputStreamLock.tryLock()) {
try {
byteInputStreamSingleton.feedBuffer(data);
if(protocol.ver >= 4 && data.length > 0 && data[0] == (byte)0xFF && channel.equals(GamePluginMessageConstants.V4_CHANNEL)) {
inputStreamSingleton.readByte();
int count = inputStreamSingleton.readVarInt();
for(int i = 0, j, k; i < count; ++i) {
j = inputStreamSingleton.readVarInt();
k = byteInputStreamSingleton.getReaderIndex() + j;
if(j > inputStreamSingleton.available()) {
throw new IOException("Packet fragment is too long: " + j + " > " + inputStreamSingleton.available());
}
pkt = protocol.readPacket(channel, GamePluginMessageConstants.CLIENT_TO_SERVER, inputStreamSingleton);
if(pkt != null) {
pkt.handlePacket(handler);
}else {
throw new IOException("Unknown packet type in fragment!");
}
if(byteInputStreamSingleton.getReaderIndex() != k) {
throw new IOException("Packet fragment was the wrong length: " + (j + byteInputStreamSingleton.getReaderIndex() - k) + " != " + j);
}
}
if(inputStreamSingleton.available() > 0) {
throw new IOException("Leftover data after reading multi-packet! (" + inputStreamSingleton.available() + " bytes)");
}
return true;
}
inputStreamSingleton.setToByteArrayReturns(data);
pkt = protocol.readPacket(channel, GamePluginMessageConstants.CLIENT_TO_SERVER, inputStreamSingleton);
if(pkt != null && byteInputStreamSingleton.available() != 0) {
throw new IOException("Packet was the wrong length: " + pkt.getClass().getSimpleName());
}
}finally {
byteInputStreamSingleton.feedBuffer(null);
inputStreamSingleton.setToByteArrayReturns(null);
inputStreamLock.unlock();
}
}else {
// slow version that makes multiple new objects
ReusableByteArrayInputStream inputStream = new ReusableByteArrayInputStream();
inputStream.feedBuffer(data);
SimpleInputBufferImpl inputBuffer = new SimpleInputBufferImpl(inputStream, data);
if(protocol.ver >= 4 && channel.equals(GamePluginMessageConstants.V4_CHANNEL)) {
inputBuffer.setToByteArrayReturns(null);
inputBuffer.readByte();
int count = inputBuffer.readVarInt();
for(int i = 0, j, k; i < count; ++i) {
j = inputBuffer.readVarInt();
k = inputStream.getReaderIndex() + j;
if(j > inputBuffer.available()) {
throw new IOException("Packet fragment is too long: " + j + " > " + inputBuffer.available());
}
pkt = protocol.readPacket(channel, GamePluginMessageConstants.CLIENT_TO_SERVER, inputBuffer);
if(pkt != null) {
pkt.handlePacket(handler);
}else {
throw new IOException("Unknown packet type in fragment!");
}
if(inputStream.getReaderIndex() != k) {
throw new IOException("Packet fragment was the wrong length: " + (j + inputStream.getReaderIndex() - k) + " != " + j);
}
}
if(inputBuffer.available() > 0) {
throw new IOException("Leftover data after reading multi-packet! (" + inputBuffer.available() + " bytes)");
}
return true;
}
pkt = protocol.readPacket(channel, GamePluginMessageConstants.CLIENT_TO_SERVER, inputBuffer);
if(pkt != null && inputStream.available() != 0) {
throw new IOException("Packet was the wrong length: " + pkt.getClass().getSimpleName());
}
}
if(pkt != null) {
pkt.handlePacket(handler);
return true;
}else {
return false;
}
}
public void sendPacketImmediately(GameMessagePacket packet) throws IOException {
sendPacket(packet, true);
}
public void sendPacket(GameMessagePacket packet) throws IOException {
sendPacket(packet, false);
}
protected void sendPacket(GameMessagePacket packet, boolean immediately) throws IOException {
int len = packet.length() + 1;
String chan;
byte[] data;
if(outputStreamLock.tryLock()) {
try {
byteOutputStreamSingleton.feedBuffer(new byte[len == 0 ? 64 : len]);
chan = protocol.writePacket(GamePluginMessageConstants.SERVER_TO_CLIENT, outputStreamSingleton, packet);
data = byteOutputStreamSingleton.returnBuffer();
}finally {
byteOutputStreamSingleton.feedBuffer(null);
outputStreamLock.unlock();
}
}else {
// slow version that makes multiple new objects
ReusableByteArrayOutputStream bao = new ReusableByteArrayOutputStream();
bao.feedBuffer(new byte[len == 0 ? 64 : len]);
SimpleOutputBufferImpl outputStream = new SimpleOutputBufferImpl(bao);
chan = protocol.writePacket(GamePluginMessageConstants.SERVER_TO_CLIENT, outputStream, packet);
data = bao.returnBuffer();
}
if(len != 0 && data.length != len && data.length + 1 != len) {
EaglerXBungee.logger().warning("Packet " + packet.getClass().getSimpleName()
+ " was the wrong length after serialization, " + data.length + " != " + len);
}
if(defagSendDelay > 0 && protocol.ver >= 4 && chan.equals(GamePluginMessageConstants.V4_CHANNEL)) {
synchronized(sendQueueV4) {
int varIntLen = GamePacketOutputBuffer.getVarIntSize(data.length);
if(immediately || sendQueueByteLengthV4 + data.length + varIntLen > 32760) {
if(futureSendTaskV4 != null && !futureSendTaskV4.isDone()) {
futureSendTaskV4.cancel(false);
futureSendTaskV4 = null;
futureSendCallableV4 = null;
}
if(!sendQueueV4.isEmpty()) {
flushQueue();
}
}
if(immediately) {
owner.sendData(chan, data);
}else {
sendQueueV4.add(data);
if(futureSendTaskV4 == null || futureSendTaskV4.isDone()) {
futureSendTaskV4 = ((EaglerInitialHandler) owner.getPendingConnection()).ch.getHandle()
.eventLoop().schedule(futureSendCallableV4 = new Callable<Void>() {
@Override
public Void call() throws Exception {
synchronized (sendQueueV4) {
if (futureSendCallableV4 != this) {
return null;
}
futureSendTaskV4 = null;
futureSendCallableV4 = null;
if(!sendQueueV4.isEmpty()) {
flushQueue();
}
}
return null;
}
}, defagSendDelay, TimeUnit.MILLISECONDS);
}
}
}
}else {
owner.sendData(chan, data);
}
}
private void flushQueue() {
if(!owner.isConnected()) {
sendQueueV4.clear();
return;
}
byte[] pkt;
if(sendQueueV4.size() == 1) {
pkt = sendQueueV4.remove(0);
owner.sendData(GamePluginMessageConstants.V4_CHANNEL, pkt);
}else {
int i, j, sendCount = 0, totalLen = 0;
while(!sendQueueV4.isEmpty()) {
do {
i = sendQueueV4.get(sendCount++).length;
totalLen += GamePacketOutputBuffer.getVarIntSize(i) + i;
}while(totalLen < 32760 && sendCount < sendQueueV4.size());
if(totalLen >= 32760) {
--sendCount;
}
if(sendCount <= 1) {
pkt = sendQueueV4.remove(0);
owner.sendData(GamePluginMessageConstants.V4_CHANNEL, pkt);
continue;
}
byte[] toSend = new byte[1 + totalLen + GamePacketOutputBuffer.getVarIntSize(sendCount)];
ByteBuf sendBuffer = Unpooled.wrappedBuffer(toSend);
sendBuffer.writerIndex(0);
sendBuffer.writeByte(0xFF);
DefinedPacket.writeVarInt(sendCount, sendBuffer);
for(j = 0; j < sendCount; ++j) {
pkt = sendQueueV4.remove(0);
DefinedPacket.writeVarInt(pkt.length, sendBuffer);
sendBuffer.writeBytes(pkt);
}
owner.sendData(GamePluginMessageConstants.V4_CHANNEL, toSend);
}
}
}
public static GameMessageHandler createServerHandler(int protocolVersion, UserConnection conn, EaglerXBungee plugin) {
switch(protocolVersion) {
case 2:
case 3:
return new ServerV3MessageHandler(conn, plugin);
case 4:
return new ServerV4MessageHandler(conn, plugin);
default:
throw new IllegalArgumentException("Unknown protocol verison: " + protocolVersion);
}
}
public static void sendHelper(UserConnection conn, GameMessagePacket packet) {
PendingConnection p = conn.getPendingConnection();
if(p instanceof EaglerInitialHandler) {
((EaglerInitialHandler)p).sendEaglerMessage(packet);
}else {
throw new UnsupportedOperationException("Tried to send eagler packet on a non-eagler connection!");
}
}
public UserConnection getUserConnection() {
return owner;
}
}

View File

@ -0,0 +1,88 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.*;
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 ServerV3MessageHandler implements GameMessageHandler {
private final UserConnection conn;
private final EaglerXBungee plugin;
public ServerV3MessageHandler(UserConnection conn, EaglerXBungee plugin) {
this.conn = conn;
this.plugin = plugin;
}
public void handleClient(CPacketGetOtherCapeEAG packet) {
plugin.getCapeService().processGetOtherCape(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
public void handleClient(CPacketGetOtherSkinEAG packet) {
plugin.getSkinService().processGetOtherSkin(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
public void handleClient(CPacketGetSkinByURLEAG packet) {
plugin.getSkinService().processGetOtherSkin(new UUID(packet.uuidMost, packet.uuidLeast), packet.url, conn);
}
public void handleClient(CPacketVoiceSignalConnectEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeConnect(conn);
}
}
public void handleClient(CPacketVoiceSignalDescEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeDesc(new UUID(packet.uuidMost, packet.uuidLeast), packet.desc, conn);
}
}
public void handleClient(CPacketVoiceSignalDisconnectV3EAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
if(packet.isPeerType) {
svc.handleVoiceSignalPacketTypeDisconnectPeer(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}else {
svc.handleVoiceSignalPacketTypeDisconnect(conn);
}
}
}
public void handleClient(CPacketVoiceSignalICEEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeICE(new UUID(packet.uuidMost, packet.uuidLeast), packet.ice, conn);
}
}
public void handleClient(CPacketVoiceSignalRequestEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeRequest(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
}
}

View File

@ -0,0 +1,182 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.protocol;
import java.util.Arrays;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventWebViewMessage;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventWebViewOpenClose;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftWebViewChannelEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftWebViewMessageEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerPauseMenuConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol.EnumSubscribedEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.*;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* 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 ServerV4MessageHandler implements GameMessageHandler {
private final UserConnection conn;
private final EaglerXBungee plugin;
public ServerV4MessageHandler(UserConnection conn, EaglerXBungee plugin) {
this.conn = conn;
this.plugin = plugin;
}
public void handleClient(CPacketGetOtherCapeEAG packet) {
plugin.getCapeService().processGetOtherCape(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
public void handleClient(CPacketGetOtherSkinEAG packet) {
plugin.getSkinService().processGetOtherSkin(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
public void handleClient(CPacketGetSkinByURLEAG packet) {
plugin.getSkinService().processGetOtherSkin(new UUID(packet.uuidMost, packet.uuidLeast), packet.url, conn);
}
public void handleClient(CPacketVoiceSignalConnectEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeConnect(conn);
}
}
public void handleClient(CPacketVoiceSignalDescEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeDesc(new UUID(packet.uuidMost, packet.uuidLeast), packet.desc, conn);
}
}
public void handleClient(CPacketVoiceSignalDisconnectV4EAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeDisconnect(conn);
}
}
public void handleClient(CPacketVoiceSignalDisconnectPeerV4EAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeDisconnectPeer(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
}
public void handleClient(CPacketVoiceSignalICEEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeICE(new UUID(packet.uuidMost, packet.uuidLeast), packet.ice, conn);
}
}
public void handleClient(CPacketVoiceSignalRequestEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)conn.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeRequest(new UUID(packet.uuidMost, packet.uuidLeast), conn);
}
}
public void handleClient(CPacketGetOtherClientUUIDV4EAG packet) {
ProxiedPlayer player = plugin.getProxy().getPlayer(new UUID(packet.playerUUIDMost, packet.playerUUIDLeast));
if(player != null) {
PendingConnection conn2 = player.getPendingConnection();
if(conn2 instanceof EaglerInitialHandler) {
UUID uuid = ((EaglerInitialHandler)conn2).getClientBrandUUID();
if (uuid != null) {
((EaglerInitialHandler) conn.getPendingConnection())
.sendEaglerMessage(new SPacketOtherPlayerClientUUIDV4EAG(packet.requestId,
uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()));
return;
}
}else {
((EaglerInitialHandler) conn.getPendingConnection())
.sendEaglerMessage(new SPacketOtherPlayerClientUUIDV4EAG(packet.requestId,
EaglerXBungeeAPIHelper.BRAND_VANILLA_UUID.getMostSignificantBits(),
EaglerXBungeeAPIHelper.BRAND_VANILLA_UUID.getLeastSignificantBits()));
return;
}
}
((EaglerInitialHandler) conn.getPendingConnection()).sendEaglerMessage(new SPacketOtherPlayerClientUUIDV4EAG(packet.requestId, 0l, 0l));
}
public void handleClient(CPacketRequestServerInfoV4EAG packet) {
EaglerPauseMenuConfig conf = plugin.getConfig().getPauseMenuConf();
if (conf != null && conf.getEnabled()
&& conf.getPacket().serverInfoMode == SPacketCustomizePauseMenuV4EAG.SERVER_INFO_MODE_SHOW_EMBED_OVER_WS
&& Arrays.equals(conf.getServerInfoHash(), packet.requestHash)) {
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)conn.getPendingConnection();
synchronized(eaglerHandler.serverInfoSendBuffer) {
if(eaglerHandler.hasSentServerInfo.getAndSet(true)) {
conn.disconnect(new TextComponent("Duplicate server info request"));
return;
}
eaglerHandler.serverInfoSendBuffer.clear();
eaglerHandler.serverInfoSendBuffer.addAll(conf.getServerInfo());
}
}else {
conn.disconnect(new TextComponent("Invalid server info request"));
}
}
public void handleClient(CPacketWebViewMessageV4EAG packet) {
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)conn.getPendingConnection();
if(eaglerHandler.isWebViewChannelAllowed) {
if(eaglerHandler.webViewMessageChannelOpen.get()) {
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.WEBVIEW_MESSAGE)) {
eaglerHandler.getRPCSessionHandler().sendRPCPacket(new SPacketRPCEventWebViewMessage(
eaglerHandler.webViewMessageChannelName, packet.type, packet.data));
}
BungeeCord.getInstance().getPluginManager().callEvent(new EaglercraftWebViewMessageEvent(conn,
eaglerHandler.getEaglerListenerConfig(), eaglerHandler.webViewMessageChannelName, packet));
}
}else {
conn.disconnect(new TextComponent("Webview channel permissions have not been enabled!"));
}
}
public void handleClient(CPacketWebViewMessageEnV4EAG packet) {
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)conn.getPendingConnection();
if(eaglerHandler.isWebViewChannelAllowed) {
eaglerHandler.webViewMessageChannelOpen.set(packet.messageChannelOpen);
String oldChannelName = eaglerHandler.webViewMessageChannelName;
eaglerHandler.webViewMessageChannelName = packet.messageChannelOpen ? packet.channelName : null;
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.WEBVIEW_OPEN_CLOSE)) {
eaglerHandler.getRPCSessionHandler().sendRPCPacket(new SPacketRPCEventWebViewOpenClose(
packet.messageChannelOpen, packet.messageChannelOpen ? packet.channelName : oldChannelName));
}
BungeeCord.getInstance().getPluginManager()
.callEvent(new EaglercraftWebViewChannelEvent(conn, eaglerHandler.getEaglerListenerConfig(),
packet.messageChannelOpen ? eaglerHandler.webViewMessageChannelName : oldChannelName,
packet.messageChannelOpen ? EaglercraftWebViewChannelEvent.EventType.CHANNEL_OPEN
: EaglercraftWebViewChannelEvent.EventType.CHANNEL_CLOSE));
}else {
conn.disconnect(new TextComponent("Webview channel permissions have not been enabled!"));
}
}
}

View File

@ -7,6 +7,7 @@ import java.util.List;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.EaglerQuerySimpleHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.MOTDConnection;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
@ -47,7 +48,7 @@ public class MOTDQueryHandler extends EaglerQuerySimpleHandler implements MOTDCo
@Override
protected void begin(String queryType) {
creationTime = System.currentTimeMillis();
creationTime = EaglerXBungeeAPIHelper.steadyTimeMillis();
subType = queryType;
returnType = "MOTD";
EaglerListenerConfig listener = getListener();
@ -60,7 +61,7 @@ public class MOTDQueryHandler extends EaglerQuerySimpleHandler implements MOTDCo
}
maxPlayers = listener.getMaxPlayers();
onlinePlayers = ProxyServer.getInstance().getOnlineCount();
players = new ArrayList();
players = new ArrayList<>();
for(ProxiedPlayer pp : ProxyServer.getInstance().getPlayers()) {
players.add(pp.getDisplayName());
if(players.size() >= 9) {

View File

@ -28,12 +28,13 @@ import net.md_5.bungee.api.plugin.PluginDescription;
*/
public class QueryManager {
private static final Map<String, Class<? extends HttpServerQueryHandler>> queryTypes = new HashMap();
private static final Map<String, Class<? extends HttpServerQueryHandler>> queryTypes = new HashMap<>();
static {
queryTypes.put("motd", MOTDQueryHandler.class);
queryTypes.put("motd.cache", MOTDQueryHandler.class);
queryTypes.put("version", VersionQueryHandler.class);
queryTypes.put("revoke_session_token", RevokeSessionQueryHandler.class);
}
public static HttpServerQueryHandler createQueryHandler(String type) {

View File

@ -0,0 +1,74 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query;
import com.google.gson.JsonObject;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftRevokeSessionQueryEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftRevokeSessionQueryEvent.EnumSessionRevokeStatus;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.EaglerQueryHandler;
import net.md_5.bungee.BungeeCord;
/**
* 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 RevokeSessionQueryHandler extends EaglerQueryHandler {
@Override
protected void begin(String queryType) {
this.setKeepAlive(true);
this.acceptBinary();
this.setMaxAge(5000l);
this.sendStringResponse("revoke_session_token", "ready");
}
@Override
protected void processString(String str) {
this.close();
}
@Override
protected void processJson(JsonObject obj) {
this.close();
}
@Override
protected void processBytes(byte[] bytes) {
if(bytes.length > 255) {
JsonObject response = new JsonObject();
response.addProperty("status", "error");
response.addProperty("code", 3);
response.addProperty("delete", false);
sendJsonResponseAndClose("revoke_session_token", response);
return;
}
this.setMaxAge(30000l);
EaglercraftRevokeSessionQueryEvent evt = new EaglercraftRevokeSessionQueryEvent(this.getAddress(), this.getOrigin(), bytes, this);
BungeeCord.getInstance().getPluginManager().callEvent(evt);
JsonObject response = new JsonObject();
EnumSessionRevokeStatus stat = evt.getResultStatus();
response.addProperty("status", stat.status);
if(stat.code != -1) {
response.addProperty("code", stat.code);
}
if(stat != EnumSessionRevokeStatus.SUCCESS) {
response.addProperty("delete", evt.getShouldDeleteCookie());
}
sendJsonResponseAndClose("revoke_session_token", response);
}
@Override
protected void closed() {
}
}

View File

@ -27,8 +27,13 @@ public class VersionQueryHandler extends EaglerQuerySimpleHandler {
protected void begin(String queryType) {
JsonObject responseObj = new JsonObject();
JsonArray handshakeVersions = new JsonArray();
handshakeVersions.add(2);
handshakeVersions.add(3);
if(this.getListener().isAllowV3()) {
handshakeVersions.add(2);
handshakeVersions.add(3);
}
if(this.getListener().isAllowV4()) {
handshakeVersions.add(4);
}
responseObj.add("handshakeVersions", handshakeVersions);
JsonArray protocolVersions = new JsonArray();
protocolVersions.add(47);

View File

@ -27,7 +27,7 @@ public class HttpContentType {
public final String cacheControlHeader;
public final long fileBrowserCacheTTL;
public static final HttpContentType defaultType = new HttpContentType(new HashSet(), "application/octet-stream", null, 14400000l);
public static final HttpContentType defaultType = new HttpContentType(new HashSet<>(), "application/octet-stream", null, 14400000l);
public HttpContentType(Set<String> extensions, String mimeType, String charset, long fileBrowserCacheTTL) {
this.extensions = extensions;

View File

@ -4,16 +4,17 @@ import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Sets;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
@ -44,13 +45,13 @@ public class HttpWebServer {
public HttpWebServer(File directory, Map<String,HttpContentType> contentTypes, List<String> index, String page404) {
this.directory = directory;
this.contentTypes = contentTypes;
this.filesCache = new HashMap();
this.filesCache = new HashMap<>();
this.index = index;
this.page404 = page404;
}
public void flushCache() {
long millis = System.currentTimeMillis();
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
synchronized(cacheClearLock) {
synchronized(filesCache) {
Iterator<HttpMemoryCache> itr = filesCache.values().iterator();
@ -69,7 +70,7 @@ public class HttpWebServer {
try {
String[] pathSplit = path.split("(\\\\|\\/)+");
List<String> pathList = pathSplit.length == 0 ? null : new ArrayList();
List<String> pathList = pathSplit.length == 0 ? null : new ArrayList<>(pathSplit.length);
for(int i = 0; i < pathSplit.length; ++i) {
pathSplit[i] = pathSplit[i].trim();
if(pathSplit[i].length() > 0) {
@ -197,7 +198,7 @@ public class HttpWebServer {
if(ct == null) {
ct = HttpContentType.defaultType;
}
long millis = System.currentTimeMillis();
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
return new HttpMemoryCache(path, requestCachePath, file, ct, millis, millis, path.lastModified());
}catch(Throwable t) {
return null;
@ -208,7 +209,7 @@ public class HttpWebServer {
if(file.fileObject == null) {
return file;
}
long millis = System.currentTimeMillis();
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
file.lastCacheHit = millis;
if(millis - file.lastDiskReload > 4000l) {
File f = file.fileObject;
@ -264,8 +265,8 @@ public class HttpWebServer {
+ "The requested resource <span id=\"addr\" style=\"font-family:monospace;font-weight:bold;background-color:#EEEEEE;padding:3px 4px;\">"
+ "</span> could not be found on this server!</p><p>" + htmlEntities(plugin.getDescription().getName()) + "/"
+ htmlEntities(plugin.getDescription().getVersion()) + "</p></body></html>").getBytes(StandardCharsets.UTF_8);
HttpContentType htmlContentType = new HttpContentType(new HashSet(Arrays.asList("html")), "text/html", "utf-8", 120000l);
long millis = System.currentTimeMillis();
HttpContentType htmlContentType = new HttpContentType(Sets.newHashSet("html"), "text/html", "utf-8", 120000l);
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
return new HttpMemoryCache(null, "~404", Unpooled.wrappedBuffer(src), htmlContentType, millis, millis, millis);
}
@ -280,8 +281,8 @@ public class HttpWebServer {
+ "404 'Websocket Upgrade Failure' (rip)</h1><h3>The URL you have requested is the physical WebSocket address of '" + name + "'</h3><p style=\"font-size:1.2em;"
+ "line-height:1.3em;\">To correctly join this server, load the latest EaglercraftX 1.8 client, click the 'Direct Connect' button<br />on the 'Multiplayer' screen, "
+ "and enter <span id=\"wsUri\">this URL</span> as the server address</p></body></html>").getBytes(StandardCharsets.UTF_8);
HttpContentType htmlContentType = new HttpContentType(new HashSet(Arrays.asList("html")), "text/html", "utf-8", 14400000l);
long millis = System.currentTimeMillis();
HttpContentType htmlContentType = new HttpContentType(Sets.newHashSet("html"), "text/html", "utf-8", 14400000l);
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
return new HttpMemoryCache(null, "~404", Unpooled.wrappedBuffer(src), htmlContentType, millis, millis, millis);
}

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, 0);
SkinPackets.setAlphaForChestV3(loadedPixels);
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, 0);
SkinPackets.setAlphaForChestV3(loadedPixels);
doAccept(loadedPixels);
return;
}else {

View File

@ -9,6 +9,8 @@ import java.util.function.Consumer;
import javax.net.ssl.SSLEngine;
import org.apache.commons.lang3.ArrayUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
@ -180,7 +182,7 @@ public class BinaryHttpClient {
buffer.readBytes(array);
buffer.release();
}else {
array = new byte[0];
array = ArrayUtils.EMPTY_BYTE_ARRAY;
}
responseCallback.accept(new Response(responseCode, array));
}finally {

View File

@ -3,7 +3,9 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.util.UUID;
import net.md_5.bungee.UserConnection;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapeCustomEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapePresetEAG;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
@ -24,56 +26,29 @@ 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;
GameMessagePacket 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));
generatedPacket = new SPacketOtherCapePresetEAG(clientUUID.getMostSignificantBits(),
clientUUID.getLeastSignificantBits(), (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);
byte[] capePixels = new byte[bs.length - 1];
System.arraycopy(bs, 1, capePixels, 0, capePixels.length);
generatedPacket = new SPacketOtherCapeCustomEAG(clientUUID.getMostSignificantBits(),
clientUUID.getLeastSignificantBits(), capePixels);
break;
default:
throw new IOException("Unknown skin packet type: " + packetType);
@ -82,29 +57,8 @@ public class CapePackets {
}
public static void registerEaglerPlayerFallback(UUID clientUUID, CapeServiceOffline capeService) {
capeService.registerEaglercraftPlayer(clientUUID, CapePackets.makePresetResponse(clientUUID, 0));
capeService.registerEaglercraftPlayer(clientUUID, new SPacketOtherCapePresetEAG(
clientUUID.getMostSignificantBits(), clientUUID.getLeastSignificantBits(), 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

@ -5,6 +5,11 @@ import java.util.Map;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketForceClientCapeCustomV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketForceClientCapePresetV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapeCustomEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherCapePresetEAG;
import net.md_5.bungee.UserConnection;
/**
@ -26,26 +31,42 @@ public class CapeServiceOffline {
public static final int masterRateLimitPerPlayer = 250;
public static final String CHANNEL = "EAG|Capes-1.8";
private final Map<UUID, GameMessagePacket> capesCache = new HashMap<>();
private final Map<UUID, byte[]> capesCache = new HashMap();
public void registerEaglercraftPlayer(UUID playerUUID, byte[] capePacket) {
public void registerEaglercraftPlayer(UUID playerUUID, GameMessagePacket capePacket) {
synchronized(capesCache) {
capesCache.put(playerUUID, capePacket);
}
}
public void processGetOtherCape(UUID searchUUID, UserConnection sender) {
if(((EaglerInitialHandler)sender.getPendingConnection()).skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
byte[] maybeCape;
EaglerInitialHandler initialHandler = (EaglerInitialHandler)sender.getPendingConnection();
if(initialHandler.skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
GameMessagePacket maybeCape;
synchronized(capesCache) {
maybeCape = capesCache.get(searchUUID);
}
if(maybeCape != null) {
sender.sendData(CapeServiceOffline.CHANNEL, maybeCape);
initialHandler.sendEaglerMessage(maybeCape);
}else {
sender.sendData(CapeServiceOffline.CHANNEL, CapePackets.makePresetResponse(searchUUID, 0));
initialHandler.sendEaglerMessage(new SPacketOtherCapePresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), 0));
}
}
}
public void processForceCape(UUID clientUUID, EaglerInitialHandler initialHandler) {
GameMessagePacket maybeCape;
synchronized(capesCache) {
maybeCape = capesCache.get(clientUUID);
}
if(maybeCape != null) {
if (maybeCape instanceof SPacketOtherCapePresetEAG) {
initialHandler.sendEaglerMessage(
new SPacketForceClientCapePresetV4EAG(((SPacketOtherCapePresetEAG) maybeCape).presetCape));
} else if (maybeCape instanceof SPacketOtherCapeCustomEAG) {
initialHandler.sendEaglerMessage(
new SPacketForceClientCapeCustomV4EAG(((SPacketOtherCapeCustomEAG) maybeCape).customCape));
}
}
}
@ -56,6 +77,37 @@ public class CapeServiceOffline {
}
}
public GameMessagePacket getCape(UUID clientUUID) {
synchronized(capesCache) {
return capesCache.get(clientUUID);
}
}
public byte[] getCapeHandshakeData(UUID clientUUID) {
GameMessagePacket capePacket = getCape(clientUUID);
if(capePacket != null) {
if(capePacket instanceof SPacketOtherCapeCustomEAG) {
SPacketOtherCapeCustomEAG pkt = (SPacketOtherCapeCustomEAG)capePacket;
byte[] ret = new byte[1174];
ret[0] = (byte)2;
System.arraycopy(pkt.customCape, 0, ret, 1, 1173);
return ret;
}else {
SPacketOtherCapePresetEAG pkt = (SPacketOtherCapePresetEAG)capePacket;
int p = pkt.presetCape;
byte[] ret = new byte[5];
ret[0] = (byte)1;
ret[1] = (byte)(p >>> 24);
ret[2] = (byte)(p >>> 16);
ret[3] = (byte)(p >>> 8);
ret[4] = (byte)(p & 0xFF);
return ret;
}
}else {
return null;
}
}
public void shutdown() {
synchronized(capesCache) {
capesCache.clear();

View File

@ -1,8 +1,9 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.md_5.bungee.UserConnection;
/**
@ -29,7 +30,7 @@ public interface ISkinService {
void processGetOtherSkin(UUID searchUUID, String skinURL, UserConnection sender);
void registerEaglercraftPlayer(UUID clientUUID, byte[] generatedPacket, int modelId) throws IOException;
void registerEaglercraftPlayer(UUID clientUUID, SkinPacketVersionCache generatedPacket, int modelId);
void unregisterPlayer(UUID clientUUID);
@ -43,4 +44,8 @@ public interface ISkinService {
void shutdown();
void processForceSkin(UUID playerUUID, EaglerInitialHandler initialHandler);
SkinPacketVersionCache getSkin(UUID playerUUID);
}

View File

@ -14,7 +14,11 @@ import java.util.UUID;
import java.util.logging.Level;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.lang3.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.sqlite.EaglerDrivers;
/**
@ -160,7 +164,7 @@ public class JDBCCacheProvider implements ICacheProvider {
throw new CacheException("SQL query failure while loading cached skin", ex);
}
if(queriedLength == 0) {
return new CacheLoadedSkin(uuid, queriedUrls, new byte[0]);
return new CacheLoadedSkin(uuid, queriedUrls, ArrayUtils.EMPTY_BYTE_ARRAY);
}else {
byte[] decompressed = new byte[queriedLength];
try {
@ -305,8 +309,9 @@ public class JDBCCacheProvider implements ICacheProvider {
@Override
public void flush() {
long millis = System.currentTimeMillis();
if(millis - lastFlush > 1200000l) { // 30 minutes
lastFlush = millis;
long steadyMillis = EaglerXBungeeAPIHelper.steadyTimeMillis();
if(steadyMillis - lastFlush > 1200000l) { // 30 minutes
lastFlush = steadyMillis;
try {
Date expiryObjects = new Date(millis - keepObjectsDays * 86400000l);
Date expiryProfiles = new Date(millis - keepProfilesDays * 86400000l);

View File

@ -1,5 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
*
@ -21,13 +23,13 @@ public class SimpleRateLimiter {
private int count;
public SimpleRateLimiter() {
timer = System.currentTimeMillis();
timer = EaglerXBungeeAPIHelper.steadyTimeMillis();
count = 0;
}
public boolean rateLimit(int maxPerMinute) {
int t = 60000 / maxPerMinute;
long millis = System.currentTimeMillis();
long millis = EaglerXBungeeAPIHelper.steadyTimeMillis();
int decr = (int)(millis - timer) / t;
if(decr > 0) {
timer += decr * t;

View File

@ -1,12 +1,13 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.md_5.bungee.UserConnection;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinCustomV3EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinCustomV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
/**
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
@ -27,79 +28,13 @@ public class SkinPackets {
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 int PACKET_GET_SKIN_BY_URL = 0x06;
public static void processPacket(byte[] data, UserConnection sender, ISkinService skinService) 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_SKIN:
processGetOtherSkin(data, sender, skinService);
break;
case PACKET_GET_SKIN_BY_URL:
processGetOtherSkinByURL(data, sender, skinService);
break;
default:
throw new IOException("Unknown packet type " + packetId);
}
}catch(IOException ex) {
throw ex;
}catch(Throwable t) {
throw new IOException("Unhandled exception handling packet type " + packetId, t);
}
}
private static void processGetOtherSkin(byte[] data, UserConnection sender, ISkinService skinService) throws IOException {
if(data.length != 17) {
throw new IOException("Invalid length " + data.length + " for skin request packet");
}
UUID searchUUID = bytesToUUID(data, 1);
skinService.processGetOtherSkin(searchUUID, sender);
}
private static void processGetOtherSkinByURL(byte[] data, UserConnection sender, ISkinService skinService) throws IOException {
if(data.length < 20) {
throw new IOException("Invalid length " + data.length + " for skin request packet");
}
UUID searchUUID = bytesToUUID(data, 1);
int urlLength = (data[17] << 8) | data[18];
if(data.length < 19 + urlLength) {
throw new IOException("Invalid length " + data.length + " for skin request packet with " + urlLength + " length URL");
}
String urlStr = bytesToAscii(data, 19, urlLength);
urlStr = SkinService.sanitizeTextureURL(urlStr);
if(urlStr == null) {
throw new IOException("Invalid URL for skin request packet");
}
URL url;
try {
url = new URL(urlStr);
}catch(MalformedURLException t) {
throw new IOException("Invalid URL for skin request packet", t);
}
String host = url.getHost();
if(EaglerXBungee.getEagler().getConfig().isValidSkinHost(host)) {
UUID validUUID = createEaglerURLSkinUUID(urlStr);
if(!searchUUID.equals(validUUID)) {
throw new IOException("Invalid generated UUID from skin URL");
}
skinService.processGetOtherSkin(searchUUID, urlStr, sender);
}else {
throw new IOException("Invalid host in skin packet: " + host);
}
}
public static void registerEaglerPlayer(UUID clientUUID, byte[] bs, ISkinService skinService) throws IOException {
public static void registerEaglerPlayer(UUID clientUUID, byte[] bs, ISkinService skinService, int protocolVers) throws IOException {
if(bs.length == 0) {
throw new IOException("Zero-length packet recieved");
}
byte[] generatedPacket;
GameMessagePacket generatedPacketV3 = null;
GameMessagePacket generatedPacketV4 = null;
int skinModel = -1;
int packetType = (int)bs[0] & 0xFF;
switch(packetType) {
@ -107,87 +42,60 @@ public class SkinPackets {
if(bs.length != 5) {
throw new IOException("Invalid length " + bs.length + " for preset skin packet");
}
generatedPacket = SkinPackets.makePresetResponse(clientUUID, (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF));
generatedPacketV3 = generatedPacketV4 = new SPacketOtherSkinPresetEAG(clientUUID.getMostSignificantBits(),
clientUUID.getLeastSignificantBits(), (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF));
break;
case PACKET_MY_SKIN_CUSTOM:
if(bs.length != 2 + 16384) {
throw new IOException("Invalid length " + bs.length + " for custom skin packet");
if(protocolVers <= 3) {
byte[] pixels = new byte[16384];
if(bs.length != 2 + pixels.length) {
throw new IOException("Invalid length " + bs.length + " for custom skin packet");
}
setAlphaForChestV3(pixels);
System.arraycopy(bs, 2, pixels, 0, pixels.length);
generatedPacketV3 = new SPacketOtherSkinCustomV3EAG(clientUUID.getMostSignificantBits(), clientUUID.getLeastSignificantBits(), (skinModel = (int)bs[1] & 0xFF), pixels);
}else {
byte[] pixels = new byte[12288];
if(bs.length != 2 + pixels.length) {
throw new IOException("Invalid length " + bs.length + " for custom skin packet");
}
setAlphaForChestV4(pixels);
System.arraycopy(bs, 2, pixels, 0, pixels.length);
generatedPacketV4 = new SPacketOtherSkinCustomV4EAG(clientUUID.getMostSignificantBits(), clientUUID.getLeastSignificantBits(), (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);
}
skinService.registerEaglercraftPlayer(clientUUID, generatedPacket, skinModel);
skinService.registerEaglercraftPlayer(clientUUID, new SkinPacketVersionCache(generatedPacketV3, generatedPacketV4), skinModel);
}
public static void registerEaglerPlayerFallback(UUID clientUUID, ISkinService skinService) throws IOException {
int skinModel = (clientUUID.hashCode() & 1) != 0 ? 1 : 0;
byte[] generatedPacket = SkinPackets.makePresetResponse(clientUUID, skinModel);
skinService.registerEaglercraftPlayer(clientUUID, generatedPacket, skinModel);
skinService.registerEaglercraftPlayer(clientUUID, SkinPacketVersionCache.createPreset(
clientUUID.getMostSignificantBits(), clientUUID.getLeastSignificantBits(), skinModel), skinModel);
}
public static void setAlphaForChest(byte[] skin64x64, byte alpha, int offset) {
if(skin64x64.length - offset != 16384) {
public static void setAlphaForChestV3(byte[] skin64x64) {
if(skin64x64.length != 16384) {
throw new IllegalArgumentException("Skin is not 64x64!");
}
for(int y = 20; y < 32; ++y) {
for(int x = 16; x < 40; ++x) {
skin64x64[offset + ((y << 8) | (x << 2))] = alpha;
skin64x64[(y << 8) | (x << 2)] = (byte)0xFF;
}
}
}
public static byte[] makePresetResponse(UUID uuid) {
return makePresetResponse(uuid, (uuid.hashCode() & 1) != 0 ? 1 : 0);
}
public static byte[] makePresetResponse(UUID uuid, int presetId) {
byte[] ret = new byte[1 + 16 + 4];
ret[0] = (byte)PACKET_OTHER_SKIN_PRESET;
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, int model, byte[] pixels) {
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, offset, ret, 18, length);
return ret;
}
public static UUID bytesToUUID(byte[] bytes, int off) {
long msb = (((long) bytes[off] & 0xFFl) << 56l) | (((long) bytes[off + 1] & 0xFFl) << 48l)
| (((long) bytes[off + 2] & 0xFFl) << 40l) | (((long) bytes[off + 3] & 0xFFl) << 32l)
| (((long) bytes[off + 4] & 0xFFl) << 24l) | (((long) bytes[off + 5] & 0xFFl) << 16l)
| (((long) bytes[off + 6] & 0xFFl) << 8l) | ((long) bytes[off + 7] & 0xFFl);
long lsb = (((long) bytes[off + 8] & 0xFFl) << 56l) | (((long) bytes[off + 9] & 0xFFl) << 48l)
| (((long) bytes[off + 10] & 0xFFl) << 40l) | (((long) bytes[off + 11] & 0xFFl) << 32l)
| (((long) bytes[off + 12] & 0xFFl) << 24l) | (((long) bytes[off + 13] & 0xFFl) << 16l)
| (((long) bytes[off + 14] & 0xFFl) << 8l) | ((long) bytes[off + 15] & 0xFFl);
return new UUID(msb, lsb);
}
private static final String hex = "0123456789abcdef";
public static String bytesToString(byte[] bytes, int off, int len) {
char[] ret = new char[len << 1];
for(int i = 0; i < len; ++i) {
ret[i * 2] = hex.charAt((bytes[off + i] >> 4) & 0xF);
ret[i * 2 + 1] = hex.charAt(bytes[off + i] & 0xF);
public static void setAlphaForChestV4(byte[] skin64x64) {
if(skin64x64.length != 12288) {
throw new IllegalArgumentException("Skin is not 64x64!");
}
for(int y = 20; y < 32; ++y) {
for(int x = 16; x < 40; ++x) {
skin64x64[((y << 6) | x) * 3] |= 0x80;
}
}
return new String(ret);
}
public static String bytesToAscii(byte[] bytes, int off, int len) {
@ -197,32 +105,11 @@ public class SkinPackets {
}
return new String(ret);
}
public static String bytesToAscii(byte[] bytes) {
return bytesToAscii(bytes, 0, bytes.length);
}
public static void UUIDToBytes(UUID uuid, byte[] bytes, int off) {
long msb = uuid.getMostSignificantBits();
long lsb = uuid.getLeastSignificantBits();
bytes[off] = (byte)(msb >>> 56l);
bytes[off + 1] = (byte)(msb >>> 48l);
bytes[off + 2] = (byte)(msb >>> 40l);
bytes[off + 3] = (byte)(msb >>> 32l);
bytes[off + 4] = (byte)(msb >>> 24l);
bytes[off + 5] = (byte)(msb >>> 16l);
bytes[off + 6] = (byte)(msb >>> 8l);
bytes[off + 7] = (byte)(msb & 0xFFl);
bytes[off + 8] = (byte)(lsb >>> 56l);
bytes[off + 9] = (byte)(lsb >>> 48l);
bytes[off + 10] = (byte)(lsb >>> 40l);
bytes[off + 11] = (byte)(lsb >>> 32l);
bytes[off + 12] = (byte)(lsb >>> 24l);
bytes[off + 13] = (byte)(lsb >>> 16l);
bytes[off + 14] = (byte)(lsb >>> 8l);
bytes[off + 15] = (byte)(lsb & 0xFFl);
}
public static byte[] asciiString(String string) {
byte[] str = new byte[string.length()];
for(int i = 0; i < str.length; ++i) {
@ -239,21 +126,4 @@ public class SkinPackets {
return "slim".equalsIgnoreCase(modelName) ? 1 : 0;
}
public static byte[] rewriteUUID(UUID newUUID, byte[] pkt) {
byte[] ret = new byte[pkt.length];
System.arraycopy(pkt, 0, ret, 0, pkt.length);
UUIDToBytes(newUUID, ret, 1);
return ret;
}
public static byte[] rewriteUUIDModel(UUID newUUID, byte[] pkt, int model) {
byte[] ret = new byte[pkt.length];
System.arraycopy(pkt, 0, ret, 0, pkt.length);
UUIDToBytes(newUUID, ret, 1);
if(ret[0] == (byte)PACKET_OTHER_SKIN_CUSTOM) {
ret[17] = (byte)model;
}
return ret;
}
}

View File

@ -1,6 +1,5 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
@ -11,6 +10,8 @@ import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.md_5.bungee.UserConnection;
/**
@ -35,16 +36,16 @@ public class SkinServiceOffline implements ISkinService {
private static class CachedSkin {
protected final UUID uuid;
protected final byte[] packet;
protected final SkinPacketVersionCache packet;
protected CachedSkin(UUID uuid, byte[] packet) {
protected CachedSkin(UUID uuid, SkinPacketVersionCache packet) {
this.uuid = uuid;
this.packet = packet;
}
}
private final Map<UUID, CachedSkin> skinCache = new HashMap();
private final Map<UUID, CachedSkin> skinCache = new HashMap<>();
private final Multimap<UUID, UUID> onlinePlayersFromTexturesMap = MultimapBuilder.hashKeys().hashSetValues().build();
@ -56,20 +57,23 @@ public class SkinServiceOffline implements ISkinService {
}
public void processGetOtherSkin(UUID searchUUID, UserConnection sender) {
if(((EaglerInitialHandler)sender.getPendingConnection()).skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
EaglerInitialHandler initialHandler = (EaglerInitialHandler)sender.getPendingConnection();
if(initialHandler.skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
CachedSkin cached;
synchronized(skinCache) {
cached = skinCache.get(searchUUID);
}
if(cached != null) {
sender.sendData(SkinService.CHANNEL, cached.packet);
initialHandler.sendEaglerMessage(cached.packet.get(initialHandler.getEaglerProtocol()));
}else {
sender.sendData(SkinService.CHANNEL, SkinPackets.makePresetResponse(searchUUID));
initialHandler.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), (searchUUID.hashCode() & 1) != 0 ? 1 : 0));
}
}
}
public void processGetOtherSkin(UUID searchUUID, String skinURL, UserConnection sender) {
EaglerInitialHandler initialHandler = (EaglerInitialHandler)sender.getPendingConnection();
Collection<UUID> uuids;
synchronized(onlinePlayersFromTexturesMap) {
uuids = onlinePlayersFromTexturesMap.get(searchUUID);
@ -81,15 +85,23 @@ public class SkinServiceOffline implements ISkinService {
while(uuidItr.hasNext()) {
cached = skinCache.get(uuidItr.next());
if(cached != null) {
sender.sendData(SkinService.CHANNEL, SkinPackets.rewriteUUID(searchUUID, cached.packet));
initialHandler.sendEaglerMessage(cached.packet.get(initialHandler.getEaglerProtocol(),
searchUUID.getMostSignificantBits(), searchUUID.getLeastSignificantBits()));
return;
}
}
}
}
sender.sendData(SkinService.CHANNEL, SkinPackets.makePresetResponse(searchUUID));
if(skinURL.startsWith("eagler://")) { // customs skulls from exported singleplayer worlds
initialHandler.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), 0));
return;
}
initialHandler.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), (searchUUID.hashCode() & 1) != 0 ? 1 : 0));
}
public void registerEaglercraftPlayer(UUID clientUUID, byte[] generatedPacket, int modelId) throws IOException {
public void registerEaglercraftPlayer(UUID clientUUID, SkinPacketVersionCache generatedPacket, int modelId) {
synchronized(skinCache) {
skinCache.put(clientUUID, new CachedSkin(clientUUID, generatedPacket));
}
@ -107,6 +119,16 @@ public class SkinServiceOffline implements ISkinService {
}
}
public void processForceSkin(UUID playerUUID, EaglerInitialHandler initialHandler) {
CachedSkin cached;
synchronized(skinCache) {
cached = skinCache.get(playerUUID);
}
if(cached != null) {
initialHandler.sendEaglerMessage(cached.packet.getForceClientV4());
}
}
public void flush() {
// no
}
@ -117,4 +139,12 @@ public class SkinServiceOffline implements ISkinService {
}
}
public SkinPacketVersionCache getSkin(UUID playerUUID) {
CachedSkin cached;
synchronized(skinCache) {
cached = skinCache.get(playerUUID);
}
return cached != null ? cached.packet : null;
}
}

View File

@ -66,7 +66,7 @@ public class EaglerDrivers {
driversJARs.put(address, classLoader);
}
Class loadedDriver;
Class<?> loadedDriver;
try {
loadedDriver = classLoader.loadClass(driverClass);
}catch(ClassNotFoundException ex) {
@ -92,8 +92,8 @@ public class EaglerDrivers {
return sqlDriver;
}
private static final Map<String, URLClassLoader> driversJARs = new HashMap();
private static final Map<String, Driver> driversDrivers = new HashMap();
private static final Map<String, URLClassLoader> driversJARs = new HashMap<>();
private static final Map<String, Driver> driversDrivers = new HashMap<>();
public static Connection connectToDatabase(String address, String driverClass, String driverPath, Properties props)
throws SQLException {

View File

@ -5,6 +5,8 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
/**
* Copyright (c) 2022 ayunami2000. All Rights Reserved.
*
@ -42,7 +44,7 @@ public class ExpiringSet<T> extends HashSet<T> {
public void checkForExpirations() {
Iterator<T> iterator = this.timestamps.keySet().iterator();
long now = System.currentTimeMillis();
long now = EaglerXBungeeAPIHelper.steadyTimeMillis();
while (iterator.hasNext()) {
T element = iterator.next();
if (super.contains(element)) {
@ -61,7 +63,7 @@ public class ExpiringSet<T> extends HashSet<T> {
public boolean add(T o) {
checkForExpirations();
boolean success = super.add(o);
if (success) timestamps.put(o, System.currentTimeMillis());
if (success) timestamps.put(o, EaglerXBungeeAPIHelper.steadyTimeMillis());
return success;
}

View File

@ -1,5 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -7,7 +9,13 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventToggledVoice;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftVoiceStatusChangeEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol.EnumSubscribedEvent;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.config.ServerInfo;
@ -29,7 +37,7 @@ import net.md_5.bungee.api.config.ServerInfo;
public class VoiceServerImpl {
private final ServerInfo server;
private final byte[] iceServersPacket;
private final GameMessagePacket iceServersPacket;
private final Map<UUID, UserConnection> voicePlayers = new HashMap<>();
private final Map<UUID, ExpiringSet<UUID>> voiceRequests = new HashMap<>();
@ -70,17 +78,23 @@ public class VoiceServerImpl {
}
}
VoiceServerImpl(ServerInfo server, byte[] iceServersPacket) {
VoiceServerImpl(ServerInfo server, GameMessagePacket iceServersPacket) {
this.server = server;
this.iceServersPacket = iceServersPacket;
}
public void handlePlayerLoggedIn(UserConnection player) {
player.sendData(VoiceService.CHANNEL, iceServersPacket);
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)player.getPendingConnection();
eaglerHandler.sendEaglerMessage(iceServersPacket);
eaglerHandler.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.DISABLED);
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
eaglerHandler.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_DISABLED);
}
}
public void handlePlayerLoggedOut(UserConnection player) {
removeUser(player.getUniqueId());
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)player.getPendingConnection();
removeUser(eaglerHandler.getUniqueId());
}
void handleVoiceSignalPacketTypeRequest(UUID player, UserConnection sender) {
@ -115,106 +129,145 @@ public class VoiceServerImpl {
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));
EaglerInitialHandler targetInitialHandler = (EaglerInitialHandler) targetPlayerCon
.getPendingConnection();
if (targetInitialHandler.getEaglerProtocol().ver <= 3) {
targetInitialHandler.sendEaglerMessage(new SPacketVoiceSignalConnectV3EAG(
senderUUID.getMostSignificantBits(), senderUUID.getLeastSignificantBits(), false, false));
} else {
targetInitialHandler.sendEaglerMessage(new SPacketVoiceSignalConnectV4EAG(
senderUUID.getMostSignificantBits(), senderUUID.getLeastSignificantBits(), false));
}
EaglerInitialHandler senderInitialHandler = (EaglerInitialHandler) sender.getPendingConnection();
if (senderInitialHandler.getEaglerProtocol().ver <= 3) {
senderInitialHandler.sendEaglerMessage(new SPacketVoiceSignalConnectV3EAG(
player.getMostSignificantBits(), player.getLeastSignificantBits(), false, true));
} else {
senderInitialHandler.sendEaglerMessage(new SPacketVoiceSignalConnectV4EAG(
player.getMostSignificantBits(), player.getLeastSignificantBits(), true));
}
}
}
}
void handleVoiceSignalPacketTypeConnect(UserConnection sender) {
if(!((EaglerInitialHandler)sender.getPendingConnection()).voiceConnectRateLimiter.rateLimit(VOICE_CONNECT_RATELIMIT)) {
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)sender.getPendingConnection();
if(!eaglerHandler.voiceConnectRateLimiter.rateLimit(VOICE_CONNECT_RATELIMIT)) {
return;
}
eaglerHandler.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.ENABLED);
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
eaglerHandler.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_ENABLED);
}
synchronized (voicePlayers) {
if (voicePlayers.containsKey(sender.getUniqueId())) {
if (voicePlayers.containsKey(eaglerHandler.getUniqueId())) {
return;
}
boolean hasNoOtherPlayers = voicePlayers.isEmpty();
voicePlayers.put(sender.getUniqueId(), sender);
voicePlayers.put(eaglerHandler.getUniqueId(), sender);
if (hasNoOtherPlayers) {
return;
}
byte[] packetToBroadcast = VoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values());
Collection<SPacketVoiceSignalGlobalEAG.UserData> userDatas = new ArrayList<>(voicePlayers.size());
for(UserConnection userCon : voicePlayers.values()) {
UUID uuid = userCon.getUniqueId();
userDatas.add(new SPacketVoiceSignalGlobalEAG.UserData(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits(), userCon.getDisplayName()));
}
GameMessagePacket packetToBroadcast = new SPacketVoiceSignalGlobalEAG(userDatas);
for (UserConnection userCon : voicePlayers.values()) {
userCon.sendData(VoiceService.CHANNEL, packetToBroadcast);
((EaglerInitialHandler)userCon.getPendingConnection()).sendEaglerMessage(packetToBroadcast);
}
}
}
void handleVoiceSignalPacketTypeICE(UUID player, String str, UserConnection sender) {
void handleVoiceSignalPacketTypeICE(UUID player, byte[] 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));
UUID uuid = sender.getUniqueId();
((EaglerInitialHandler) pass.getPendingConnection()).sendEaglerMessage(
new SPacketVoiceSignalICEEAG(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits(), str));
}
}
void handleVoiceSignalPacketTypeDesc(UUID player, String str, UserConnection sender) {
void handleVoiceSignalPacketTypeDesc(UUID player, byte[] 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));
UUID uuid = sender.getUniqueId();
((EaglerInitialHandler) pass.getPendingConnection()).sendEaglerMessage(
new SPacketVoiceSignalDescEAG(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits(), 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());
}
void handleVoiceSignalPacketTypeDisconnect(UserConnection sender) {
removeUser(sender.getUniqueId());
}
public void removeUser(UUID user) {
void handleVoiceSignalPacketTypeDisconnectPeer(UUID player, UserConnection sender) {
synchronized (voicePlayers) {
if (voicePlayers.remove(user) == null) {
if (!voicePlayers.containsKey(player)) {
return;
}
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) {
((EaglerInitialHandler) conn.getPendingConnection()).sendEaglerMessage(
new SPacketVoiceSignalDisconnectPeerEAG(player.getMostSignificantBits(),
player.getLeastSignificantBits()));
}
((EaglerInitialHandler) sender.getPendingConnection())
.sendEaglerMessage(new SPacketVoiceSignalDisconnectPeerEAG(target.getMostSignificantBits(),
target.getLeastSignificantBits()));
}
}
}
}
void removeUser(UUID user) {
synchronized (voicePlayers) {
UserConnection connRemove;
if ((connRemove = voicePlayers.remove(user)) == null) {
return;
}else {
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)connRemove.getPendingConnection();
eaglerHandler.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.DISABLED);
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
eaglerHandler.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_DISABLED);
}
}
voiceRequests.remove(user);
if (voicePlayers.size() > 0) {
byte[] voicePlayersPkt = VoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values());
Collection<SPacketVoiceSignalGlobalEAG.UserData> userDatas = new ArrayList<>(voicePlayers.size());
for(UserConnection userCon : voicePlayers.values()) {
UUID uuid = userCon.getUniqueId();
userDatas.add(new SPacketVoiceSignalGlobalEAG.UserData(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits(), userCon.getDisplayName()));
}
GameMessagePacket voicePlayersPkt = new SPacketVoiceSignalGlobalEAG(userDatas);
for (UserConnection userCon : voicePlayers.values()) {
if (!user.equals(userCon.getUniqueId())) {
userCon.sendData(VoiceService.CHANNEL, voicePlayersPkt);
((EaglerInitialHandler)userCon.getPendingConnection()).sendEaglerMessage(voicePlayersPkt);
}
}
}
byte[] userDisconnectPacket = null;
Iterator<VoicePair> pairsItr = voicePairs.iterator();
while (pairsItr.hasNext()) {
VoicePair voicePair = pairsItr.next();
@ -229,10 +282,9 @@ public class VoiceServerImpl {
if (voicePlayers.size() > 0) {
UserConnection conn = voicePlayers.get(target);
if (conn != null) {
if (userDisconnectPacket == null) {
userDisconnectPacket = VoiceSignalPackets.makeVoiceSignalPacketDisconnect(user);
}
conn.sendData(VoiceService.CHANNEL, userDisconnectPacket);
((EaglerInitialHandler) conn.getPendingConnection()).sendEaglerMessage(
new SPacketVoiceSignalDisconnectPeerEAG(user.getMostSignificantBits(),
user.getLeastSignificantBits()));
}
}
}
@ -240,4 +292,13 @@ public class VoiceServerImpl {
}
}
EnumVoiceState getPlayerVoiceState(UUID uniqueId) {
synchronized (voicePlayers) {
if(voicePlayers.containsKey(uniqueId)) {
return EnumVoiceState.ENABLED;
}
}
return EnumVoiceState.DISABLED;
}
}

View File

@ -7,7 +7,14 @@ import java.util.Set;
import java.util.UUID;
import gnu.trove.map.TMap;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventToggledVoice;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftVoiceStatusChangeEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.backend_rpc_protocol.EnumSubscribedEvent;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketVoiceSignalAllowedEAG;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.config.ServerInfo;
@ -29,17 +36,15 @@ import net.md_5.bungee.api.config.ServerInfo;
*/
public class VoiceService {
public static final String CHANNEL = "EAG|Voice-1.8";
private final Map<String, VoiceServerImpl> serverMap = new HashMap();
private final byte[] disableVoicePacket;
private final Map<String, VoiceServerImpl> serverMap = new HashMap<>();
private final GameMessagePacket disableVoicePacket;
public VoiceService(EaglerBungeeConfig conf) {
this.disableVoicePacket = VoiceSignalPackets.makeVoiceSignalPacketAllowed(false, null);
this.disableVoicePacket = new SPacketVoiceSignalAllowedEAG(false, null);
String[] iceServers = conf.getICEServers().toArray(new String[conf.getICEServers().size()]);
byte[] iceServersPacket = VoiceSignalPackets.makeVoiceSignalPacketAllowed(true, iceServers);
SPacketVoiceSignalAllowedEAG iceServersPacket = new SPacketVoiceSignalAllowedEAG(true, iceServers);
TMap<String,ServerInfo> servers = BungeeCord.getInstance().config.getServers();
Set<String> keySet = new HashSet(servers.keySet());
Set<String> keySet = new HashSet<>(servers.keySet());
keySet.removeAll(conf.getDisableVoiceOnServersSet());
for(String s : keySet) {
serverMap.put(s, new VoiceServerImpl(servers.get(s), iceServersPacket));
@ -59,7 +64,12 @@ public class VoiceService {
if(svr != null) {
svr.handlePlayerLoggedIn(player);
}else {
player.sendData(CHANNEL, disableVoicePacket);
EaglerInitialHandler eaglerHandler = (EaglerInitialHandler)player.getPendingConnection();
eaglerHandler.sendEaglerMessage(disableVoicePacket);
eaglerHandler.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.SERVER_DISABLE);
if(eaglerHandler.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
eaglerHandler.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_SERVER_DISABLE);
}
}
}
@ -70,7 +80,7 @@ public class VoiceService {
}
}
void handleVoiceSignalPacketTypeRequest(UUID player, UserConnection sender) {
public void handleVoiceSignalPacketTypeRequest(UUID player, UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
@ -79,7 +89,7 @@ public class VoiceService {
}
}
void handleVoiceSignalPacketTypeConnect(UserConnection sender) {
public void handleVoiceSignalPacketTypeConnect(UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
@ -88,7 +98,7 @@ public class VoiceService {
}
}
void handleVoiceSignalPacketTypeICE(UUID player, String str, UserConnection sender) {
public void handleVoiceSignalPacketTypeICE(UUID player, byte[] str, UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
@ -97,7 +107,7 @@ public class VoiceService {
}
}
void handleVoiceSignalPacketTypeDesc(UUID player, String str, UserConnection sender) {
public void handleVoiceSignalPacketTypeDesc(UUID player, byte[] str, UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
@ -106,13 +116,31 @@ public class VoiceService {
}
}
void handleVoiceSignalPacketTypeDisconnect(UUID player, UserConnection sender) {
public void handleVoiceSignalPacketTypeDisconnect(UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
svr.handleVoiceSignalPacketTypeDisconnect(player, sender);
svr.handleVoiceSignalPacketTypeDisconnect(sender);
}
}
}
public void handleVoiceSignalPacketTypeDisconnectPeer(UUID player, UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
svr.handleVoiceSignalPacketTypeDisconnectPeer(player, sender);
}
}
}
public EnumVoiceState getPlayerVoiceState(UUID player, ServerInfo info) {
VoiceServerImpl svr = serverMap.get(info.getName());
if(svr != null) {
return svr.getPlayerVoiceState(player);
}else {
return EnumVoiceState.SERVER_DISABLE;
}
}
}

View File

@ -1,194 +0,0 @@
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

@ -1,11 +1,11 @@
voice_stun_servers:
voice_servers_no_passwd:
- '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:
voice_servers_passwd:
openrelay1:
url: 'turn:openrelay.metered.ca:80'
username: 'openrelayproject'

View File

@ -13,6 +13,10 @@ listener_01:
- '&6An EaglercraftX server'
allow_motd: true
allow_query: true
allow_protocol_v3: true
allow_protocol_v4: true
protocol_v4_defrag_send_delay: 10
allow_cookie_revoke_query: true
request_motd_cache:
cache_ttl: 7200
online_server_list_animation: false

View File

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Eaglercraft Server</title>
<style type="text/css">
body {
margin: 16px;
font-family: sans-serif;
}
</style>
</head>
<script type="text/javascript">
{% embed text `message_api_v1.js` %}
</script>
<script type="text/javascript">
// Open the channel, this can be any string
serverMessageAPI.openChannel("com.example.test_channel");
// Set the callback for when messages are recieved
serverMessageAPI.addEventListener("message", function(msg) {
var newElement = document.createElement("li");
if(msg.type === "binary") {
newElement.innerText = "[" + msg.channel + "][binary] ArrayBuffer(" + msg.data.byteLength + ")";
}else if(msg.type === "string") {
newElement.innerText = "[" + msg.channel + "][string] \"" + msg.data + "\"";
}
document.getElementById("messages_recieved").appendChild(newElement);
});
window.addEventListener("load", function() {
document.getElementById("message_send").addEventListener("click", function() {
var el = document.getElementById("message_contents");
var toSend = el.value.trim();
if(toSend.length > 0) {
// Send the message, can be a string, ArrayBuffer, Int8Array, or Uint8Array
serverMessageAPI.send("com.example.test_channel", toSend);
el.value = "";
}
});
});
/*
* // Add this event listener to your bungee plugin:
*
* @EventHandler
* public void testWebViewMessageAPI(EaglercraftWebViewMessageEvent event) {
* if(event.getType() == MessageType.STRING && event.getChannelName().equals("com.example.test_channel")) {
* event.sendResponse(event.getAsString());
* }
* }
*
*/
</script>
<body>
<h1>Message API Test</h1>
<h4>Server Version: {% global `plugin_name` %} {% global `plugin_version` %}</h4>
<h4>Make sure you enable javascript in "pause_menu.yml"</h4>
<p>Message: <input type="text" id="message_contents" placeholder="eagler"> <button id="message_send">Send</button></p>
<p>Recieved from server:</p>
<ul id="messages_recieved"></ul>
</body>
</html>

View File

@ -0,0 +1,63 @@
"use strict";
window.serverMessageAPI = (function() {
var channelOpen = null;
var messageHandlers = [];
window.addEventListener("message", function(evt) {
var dat = evt.data;
if((typeof dat === "object") && dat.ver === 1 && (typeof dat.type === "string") && (typeof dat.channel === "string") && dat.channel.length > 0) {
for(var i = 0; i < messageHandlers.length; ++i) {
messageHandlers[i](dat);
}
}
});
var ServerMessageAPIError = function(message) {
this.name = "ServerMessageAPIError";
this.message = message;
};
ServerMessageAPIError.prototype = Error.prototype;
var openCh = function(chName) {
if(channelOpen !== null) throw new ServerMessageAPIError("Cannot open multiple channels, this feature is not supported!");
channelOpen = chName;
window.parent.postMessage({ver:1,channel:chName,open:true}, "*");
};
var closeCh = function(chName) {
if(channelOpen !== chName) throw new ServerMessageAPIError("Cannot close channel \"" + chName + "\", that channel is not open!");
channelOpen = null;
window.parent.postMessage({ver:1,channel:chName,open:false}, "*");
};
var addListener = function(name, handler) {
if(name === "message") messageHandlers.push(handler);
};
var remListener = function(name, handler) {
if(name === "message") messageHandlers = messageHandlers.filter(function(o) { return o !== handler; });
};
var fixTypedArray = function(arr) {
if(arr.length === arr.buffer.byteLength) {
return arr.buffer;
}else {
var toSend = (data instanceof Uint8Array) ? new Uint8Array(arr.length) : new Int8Array(arr.length);
toSend.set(arr);
return toSend.buffer;
}
};
var send = function(chName, data) {
if(channelOpen !== chName) throw new ServerMessageAPIError("Cannot send message on channel \"" + chName + "\", that channel is not open!");
if(typeof data === "string") {
window.parent.postMessage({ver:1,channel:chName,data:data}, "*");
}else if(data instanceof ArrayBuffer) {
window.parent.postMessage({ver:1,channel:chName,data:data}, "*");
}else if((data instanceof Uint8Array) || (data instanceof Int8Array)) {
window.parent.postMessage({ver:1,channel:chName,data:fixTypedArray(data)}, "*");
}else {
throw new ServerMessageAPIError("Only strings, ArrayBuffers, Uint8Arrays, and Int8Arrays can be sent with this function!");
}
};
return {
ServerMessageAPIError: ServerMessageAPIError,
openChannel: openCh,
closeChannel: closeCh,
addEventListener: addListener,
removeEventListener: remListener,
send: send
};
})();

View File

@ -0,0 +1,43 @@
enable_custom_pause_menu: false
server_info_button:
enable_button: true
button_text: 'Server Info'
button_mode_open_new_tab: false
server_info_embed_url: ''
button_mode_embed_file: true
server_info_embed_file: 'server_info.html'
server_info_embed_screen_title: 'Server Info'
server_info_embed_send_chunk_rate: 1
server_info_embed_send_chunk_size: 24576
enable_template_macros: true
server_info_embed_template_globals:
example_global: 'eagler'
allow_embed_template_eval_macro: false
enable_webview_javascript: false
enable_webview_message_api: false
enable_webview_strict_csp: true
discord_button:
enable_button: true
button_text: 'Discord'
button_url: 'https://invite url here'
custom_images:
icon_title_L: ''
icon_title_R: ''
icon_backToGame_L: ''
icon_backToGame_R: ''
icon_achievements_L: ''
icon_achievements_R: ''
icon_statistics_L: ''
icon_statistics_R: ''
icon_serverInfo_L: ''
icon_serverInfo_R: ''
icon_options_L: ''
icon_options_R: ''
icon_discord_L: ''
icon_discord_R: ''
icon_disconnect_L: ''
icon_disconnect_R: ''
icon_background_pause: 'test_image.png'
icon_background_all: 'test_image.png'
icon_watermark_pause: ''
icon_watermark_all: ''

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Eaglercraft Server</title>
<style type="text/css">
body {
margin: 16px;
font-family: sans-serif;
}
</style>
</head>
<body>
<h1>Hello World</h1>
{% htmlescape on %}
<p>Server Name: {% global `server_name` %}</p>
<p>Using: {% global `plugin_name` %} {% global `plugin_version` %}</p>
<p>JVM: {% property `java.vm.name` `(unknown)` %} ({% property `java.vm.info` `null` %}) {% property `java.vm.vendor` `(unknown)` %}</p>
{% htmlescape off %}
<p><img src="data:image/png;base64,{% embed base64 `test_image.png` %}" /></p>
<!-- Note: JPEGs are recommended for larger images to reduce their size -->
<!-- <p><img src="data:image/jpeg;base64,(% embed base64 `large_image.jpg` %)" /></p> -->
</body>
</html>

View File

@ -23,4 +23,5 @@ 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: []
disable_fnaw_skins_on_servers: []
enable_backend_rpc_api: false

View File

@ -1,5 +1,5 @@
name: EaglercraftXBungee
main: net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee
version: 1.2.7
version: 1.3.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