<1.1.0> Add protocol V4 support to EaglerXVelocity

This commit is contained in:
lax1dude
2024-09-21 12:03:57 -07:00
parent 1cf3a85c2a
commit 173727c8c4
87 changed files with 11988 additions and 1102 deletions

View File

@ -7,6 +7,7 @@ import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
@ -19,6 +20,7 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.network.TransportType;
@ -33,7 +35,10 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.EaglerBackendRPCProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.auth.DefaultAuthSystem;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.command.CommandClientBrand;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.command.CommandConfirmCode;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.command.CommandDomain;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.command.CommandEaglerPurge;
@ -45,6 +50,7 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerVeloci
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.handlers.EaglerPacketEventListener;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerUpdateSvc;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.web.HttpWebServer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.shit.CompatWarning;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.BinaryHttpClient;
@ -53,6 +59,7 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.ISkinService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.SkinService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.SkinServiceOffline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
/**
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
@ -102,6 +109,7 @@ public class EaglerXVelocity {
private Timer closeInactiveConnections;
private Timer skinServiceTasks = null;
private Timer authServiceTasks = null;
private Timer updateServiceTasks = null;
private final ChannelFutureListener newChannelListener;
private ISkinService skinService;
private CapeServiceOffline capeService;
@ -116,16 +124,16 @@ public class EaglerXVelocity {
dataDirAsPath = dataDirIn;
dataDir = dataDirIn.toFile();
openChannels = new LinkedList();
openChannels = new LinkedList<>();
newChannelListener = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture ch) throws Exception {
synchronized(openChannels) { // synchronize whole block to preserve logging order
if(ch.isSuccess()) {
EaglerXVelocity.logger().info("Eaglercraft is listening on: {}", ch.channel().attr(EaglerPipeline.LOCAL_ADDRESS).get().toString());
EaglerXVelocity.logger().info("Eaglercraft is listening on: {}", ch.channel().attr(EaglerPipeline.LOCAL_ADDRESS).get());
openChannels.add(ch.channel());
}else {
EaglerXVelocity.logger().error("Eaglercraft could not bind port: {}", ch.channel().attr(EaglerPipeline.LOCAL_ADDRESS).get().toString());
EaglerXVelocity.logger().error("Eaglercraft could not bind port: {}", ch.channel().attr(EaglerPipeline.LOCAL_ADDRESS).get());
EaglerXVelocity.logger().error("Reason: {}", ch.cause().toString());
}
}
@ -162,11 +170,17 @@ public class EaglerXVelocity {
} catch(Throwable t) {
throw new RuntimeException("Accessing private fields failed!", t);
}
Map<String, String> templateGlobals = EaglerXVelocityAPIHelper.getTemplateGlobals();
templateGlobals.put("plugin_name", EaglerXVelocityVersion.NAME);
templateGlobals.put("plugin_version", EaglerXVelocityVersion.VERSION);
templateGlobals.put("plugin_authors", String.join(", ", EaglerXVelocityVersion.AUTHORS));
templateGlobals.put("plugin_description", EaglerXVelocityVersion.DESCRIPTION);
reloadConfig();
proxy.getEventManager().register(this, new EaglerPacketEventListener(this));
EaglerCommand.register(this, new CommandRatelimit());
EaglerCommand.register(this, new CommandConfirmCode());
EaglerCommand.register(this, new CommandDomain());
EaglerCommand.register(this, new CommandClientBrand());
EaglerAuthConfig authConf = conf.getAuthConfig();
conf.setCracked(!proxy.getConfiguration().isOnlineMode() || !authConf.isEnableAuthentication());
if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) {
@ -178,9 +192,12 @@ public class EaglerXVelocity {
EaglerCommand.register(this, new CommandEaglerPurge(authConf.getEaglerCommandName()));
}
}
proxy.getChannelRegistrar().register(SkinService.CHANNEL, CapeServiceOffline.CHANNEL,
EaglerPipeline.UPDATE_CERT_CHANNEL, VoiceService.CHANNEL,
EaglerPacketEventListener.FNAW_SKIN_ENABLE_CHANNEL, EaglerPacketEventListener.GET_DOMAIN_CHANNEL);
for(String str : GamePluginMessageProtocol.getAllChannels()) {
proxy.getChannelRegistrar().register(new LegacyChannelIdentifier(str));
}
proxy.getChannelRegistrar().register(new LegacyChannelIdentifier(EaglerBackendRPCProtocol.CHANNEL_NAME));
proxy.getChannelRegistrar().register(new LegacyChannelIdentifier(EaglerBackendRPCProtocol.CHANNEL_NAME_READY));
proxy.getChannelRegistrar().register(EaglerPacketEventListener.GET_DOMAIN_CHANNEL);
if(closeInactiveConnections != null) {
closeInactiveConnections.cancel();
@ -253,11 +270,32 @@ public class EaglerXVelocity {
}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(EaglerXVelocityVersion.ID + ": Update Service Tasks");
updateServiceTasks.schedule(new TimerTask() {
@Override
public void run() {
try {
EaglerUpdateSvc.updateTick();
}catch(Throwable t) {
logger.error("Error ticking update service!", t);
}
}
}, 0l, 5000l);
}
}
@Subscribe
public void onProxyShutdown(ProxyShutdownEvent e) {
stopListeners();
if(updateServiceTasks != null) {
updateServiceTasks.cancel();
updateServiceTasks = null;
}
if(closeInactiveConnections != null) {
closeInactiveConnections.cancel();
closeInactiveConnections = null;
@ -266,6 +304,10 @@ public class EaglerXVelocity {
skinServiceTasks.cancel();
skinServiceTasks = null;
}
if(updateServiceTasks != null) {
updateServiceTasks.cancel();
updateServiceTasks = null;
}
skinService.shutdown();
skinService = null;
capeService.shutdown();
@ -334,7 +376,7 @@ public class EaglerXVelocity {
synchronized(openChannels) {
for(Channel c : openChannels) {
c.close().syncUninterruptibly();
EaglerXVelocity.logger().info("Eaglercraft listener closed: " + c.attr(EaglerPipeline.LOCAL_ADDRESS).get().toString());
EaglerXVelocity.logger().info("Eaglercraft listener closed: " + c.attr(EaglerPipeline.LOCAL_ADDRESS).get());
}
openChannels.clear();
}

View File

@ -17,13 +17,13 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity;
*/
public class EaglerXVelocityVersion {
public static final String NATIVE_VELOCITY_BUILD = "3.3.0-SNAPSHOT:9d25d309:b400";
public static final String NATIVE_VELOCITY_BUILD = "3.3.0-SNAPSHOT:2016d148:b436";
public static final String ID = "EaglerXVelocity";
public static final String PLUGIN_ID = "eaglerxvelocity";
public static final String NAME = "EaglercraftXVelocity";
public static final String 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";
public static final String VERSION = "1.0.6";
public static final String VERSION = "1.1.0";
public static final String[] AUTHORS = new String[] { "lax1dude", "ayunami2000" };
}

View File

@ -0,0 +1,22 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.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_velocity.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,27 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api;
import com.velocitypowered.proxy.protocol.util.VelocityLegacyHoverEventSerializer;
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
/**
* 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 JSONLegacySerializer {
public static final JSONComponentSerializer instance = JSONComponentSerializer.builder()
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE).build();
}

View File

@ -0,0 +1,361 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeShowV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketNotifBadgeShowV4EAG.EnumBadgePriority;
/**
* 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 Component bodyComponent = null;
private Component titleComponent = null;
private Component 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 = JSONLegacySerializer.instance.deserialize(packet.bodyComponent);
}catch(Throwable t) {
bodyComponent = Component.text(packet.bodyComponent);
}
try {
titleComponent = JSONLegacySerializer.instance.deserialize(packet.titleComponent);
}catch(Throwable t) {
titleComponent = Component.text(packet.titleComponent);
}
try {
sourceComponent = JSONLegacySerializer.instance.deserialize(packet.sourceComponent);
}catch(Throwable t) {
sourceComponent = Component.text(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 Component getBodyComponent() {
return bodyComponent;
}
public NotificationBadgeBuilder setBodyComponent(Component bodyComponent) {
this.bodyComponent = bodyComponent;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setBodyComponent(String bodyText) {
this.bodyComponent = Component.text(bodyText);
this.packetDirty = true;
return this;
}
public Component getTitleComponent() {
return titleComponent;
}
public NotificationBadgeBuilder setTitleComponent(Component titleComponent) {
this.titleComponent = titleComponent;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setTitleComponent(String titleText) {
this.titleComponent = Component.text(titleText);
this.packetDirty = true;
return this;
}
public Component getSourceComponent() {
return sourceComponent;
}
public NotificationBadgeBuilder setSourceComponent(Component sourceComponent) {
this.sourceComponent = sourceComponent;
this.packetDirty = true;
return this;
}
public NotificationBadgeBuilder setSourceComponent(String sourceText) {
this.sourceComponent = Component.text(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 ? JSONLegacySerializer.instance.serialize(bodyComponent) : "";
System.out.println(bodyComp);
if(bodyComp.length() > 32767) {
throw new IllegalStateException("Body component is longer than 32767 chars serialized!");
}
String titleComp = titleComponent != null ? JSONLegacySerializer.instance.serialize(titleComponent) : "";
if(titleComp.length() > 255) {
throw new IllegalStateException("Title component is longer than 255 chars serialized!");
}
String sourceComp = sourceComponent != null ? JSONLegacySerializer.instance.serialize(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,87 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event;
import java.net.InetAddress;
import net.kyori.adventure.text.Component;
/**
* 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 {
private final String clientBrand;
private final String clientVersion;
private final String origin;
private final int protocolVersion;
private final InetAddress remoteAddress;
private boolean cancelled;
private Component 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 Component getMessage() {
return message;
}
public void setMessage(Component 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;
}
public boolean isCancelled() {
return cancelled;
}
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
public void setKickMessage(String message) {
this.cancelled = true;
this.message = Component.text(message);
}
public void setKickMessage(Component message) {
this.cancelled = true;
this.message = message;
}
}

View File

@ -0,0 +1,205 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.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_velocity.config.EaglerListenerConfig;
/**
* 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 {
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_velocity.api.event;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.function.Consumer;
@ -28,17 +29,19 @@ public class EaglercraftHandleAuthPasswordEvent {
}
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!";
@ -50,10 +53,10 @@ public class EaglercraftHandleAuthPasswordEvent {
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;
@ -62,6 +65,8 @@ public class EaglercraftHandleAuthPasswordEvent {
this.authProfileUsername = authProfileUsername;
this.authProfileUUID = authProfileUUID;
this.authPasswordData = authPasswordData;
this.enableCookies = enableCookies;
this.cookieData = cookieData;
this.eventAuthMethod = eventAuthMethod;
this.eventAuthMessage = eventAuthMessage;
this.authAttachment = authAttachment;
@ -89,11 +94,23 @@ public class EaglercraftHandleAuthPasswordEvent {
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

@ -6,7 +6,7 @@ import java.util.function.Consumer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerListenerConfig;
/**
* 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
@ -52,6 +52,7 @@ public class EaglercraftIsAuthRequiredEvent {
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;
@ -116,6 +117,14 @@ public class EaglercraftIsAuthRequiredEvent {
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,8 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
@ -19,13 +21,15 @@ import java.util.UUID;
*/
public class EaglercraftRegisterCapeEvent {
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() {
@ -45,10 +49,13 @@ public class EaglercraftRegisterCapeEvent {
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);
EaglerXVelocityAPIHelper.convertCape32x32RGBAto23x17RGB(tex, 0, customTex, 1);
}
public void setForceUseCustomByPacket(byte[] packet) {
@ -58,4 +65,8 @@ public class EaglercraftRegisterCapeEvent {
public byte[] getForceSetUseCustomPacket() {
return customTex;
}
public <T> T getAuthAttachment() {
return (T)authAttachment;
}
}

View File

@ -21,30 +21,29 @@ import com.velocitypowered.api.util.GameProfile.Property;
*/
public class EaglercraftRegisterSkinEvent {
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) {
@ -56,9 +55,11 @@ public class EaglercraftRegisterSkinEvent {
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;
@ -66,21 +67,12 @@ public class EaglercraftRegisterSkinEvent {
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() {
@ -103,8 +95,8 @@ public class EaglercraftRegisterSkinEvent {
return customTex;
}
public String getForceSetUseURL() {
return customURL;
public <T> T getAuthAttachment() {
return (T)authAttachment;
}
}

View File

@ -0,0 +1,90 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.query.RevokeSessionQueryHandler;
/**
* 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 {
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_velocity.api.event;
import com.velocitypowered.api.proxy.Player;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
/**
* 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 {
public static enum EnumVoiceState {
SERVER_DISABLE, DISABLED, ENABLED;
}
private final Player playerObj;
private final EaglerListenerConfig listener;
private final EaglerPlayerData eaglerHandler;
private final EnumVoiceState voiceStateOld;
private final EnumVoiceState voiceStateNew;
public EaglercraftVoiceStatusChangeEvent(Player playerObj, EaglerListenerConfig listener,
EaglerPlayerData eaglerHandler, EnumVoiceState voiceStateOld, EnumVoiceState voiceStateNew) {
this.playerObj = playerObj;
this.listener = listener;
this.eaglerHandler = eaglerHandler;
this.voiceStateOld = voiceStateOld;
this.voiceStateNew = voiceStateNew;
}
public Player getPlayerObj() {
return playerObj;
}
public EaglerListenerConfig getListener() {
return listener;
}
public EaglerPlayerData getEaglerHandler() {
return eaglerHandler;
}
public EnumVoiceState getVoiceStateOld() {
return voiceStateOld;
}
public EnumVoiceState getVoiceStateNew() {
return voiceStateNew;
}
}

View File

@ -0,0 +1,66 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event;
import io.netty.channel.Channel;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerListenerConfig;
/**
* 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 {
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;
}
public boolean isCancelled() {
return cancelled;
}
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_velocity.api.event;
import com.velocitypowered.api.proxy.Player;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerListenerConfig;
/**
* 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 {
public static enum EventType {
CHANNEL_OPEN, CHANNEL_CLOSE;
}
private final Player player;
private final EaglerListenerConfig listener;
private final String channel;
private final EventType type;
public EaglercraftWebViewChannelEvent(Player player, EaglerListenerConfig listener, String channel, EventType type) {
this.player = player;
this.listener = listener;
this.channel = channel;
this.type = type;
}
public Player getPlayer() {
return player;
}
public EaglerListenerConfig getListener() {
return listener;
}
public String getChannel() {
return channel;
}
public EventType getType() {
return type;
}
}

View File

@ -0,0 +1,119 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event;
import java.nio.charset.StandardCharsets;
import com.velocitypowered.api.proxy.Player;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketWebViewMessageV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketWebViewMessageV4EAG;
/**
* 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 {
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 Player player;
private final EaglerListenerConfig listener;
private final String currentChannel;
private final EaglerPlayerData eaglerHandler;
private final MessageType type;
private final byte[] data;
private String asString;
public EaglercraftWebViewMessageEvent(Player player, EaglerListenerConfig listener, String currentChannel, MessageType type, byte[] data) {
this.player = player;
this.listener = listener;
this.currentChannel = currentChannel;
this.eaglerHandler = EaglerXVelocityAPIHelper.getEaglerHandle(player);
this.type = type;
this.data = data;
}
public EaglercraftWebViewMessageEvent(Player player, EaglerListenerConfig listener, String currentChannel, CPacketWebViewMessageV4EAG packet) {
this.player = player;
this.listener = listener;
this.currentChannel = currentChannel;
this.eaglerHandler = EaglerXVelocityAPIHelper.getEaglerHandle(player);
this.type = MessageType.fromId(packet.type);
this.data = packet.data;
}
public Player getPlayer() {
return player;
}
public EaglerListenerConfig getListener() {
return listener;
}
public EaglerPlayerData 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_velocity.api.query;
import java.net.InetAddress;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerListenerConfig;
/**
@ -31,7 +32,7 @@ public interface MOTDConnection {
long getConnectionTimestamp();
public default long getConnectionAge() {
return System.currentTimeMillis() - getConnectionTimestamp();
return EaglerXVelocityAPIHelper.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_velocity.api.EaglerXVelocityAPIHelper;
/**
* 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 = EaglerXVelocityAPIHelper.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 = EaglerXVelocityAPIHelper.steadyTimeMillis();
return etr.instance;
}
}
@ -90,7 +92,7 @@ public class AuthLoadingCache<K, V> {
}
public void tick() {
long millis = System.currentTimeMillis();
long millis = EaglerXVelocityAPIHelper.steadyTimeMillis();
if(millis - cacheTimer > (cacheTTL / 2L)) {
cacheTimer = millis;
synchronized(cacheMap) {

View File

@ -246,7 +246,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();
}
@ -338,7 +338,7 @@ public class DefaultAuthSystem {
}
}
}catch(SQLException ex) {
EaglerXVelocity.logger().error("Could not update last login for \"{}\"", info.mojangUUID.toString(), ex);
EaglerXVelocity.logger().error("Could not update last login for \"{}\"", info.mojangUUID, ex);
}
event.setLoginAllowed();
@ -538,7 +538,7 @@ public class DefaultAuthSystem {
if(!playerName.equals(username)) {
EaglerXVelocity.logger().info(
"Player \"{}\" changed their username from \"{}\" to \"{}\", updating authentication database...",
uuid.toString(), username, playerName);
uuid, username, playerName);
synchronized(updateMojangUsername) {
updateMojangUsername.setString(1, playerName);
updateMojangUsername.setString(2, uuidString);
@ -558,7 +558,7 @@ public class DefaultAuthSystem {
}
}
}catch(SQLException ex) {
EaglerXVelocity.logger().error("Could not look up UUID \"{}\" in auth database!", uuid.toString(), ex);
EaglerXVelocity.logger().error("Could not look up UUID \"{}\" in auth database!", uuid, ex);
}
}
if(isRegistered) {

View File

@ -0,0 +1,82 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.command;
import java.util.Optional;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
/**
* 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 EaglerCommand {
public CommandClientBrand() {
super("client-brand", "eaglercraft.command.clientbrand", "clientbrand");
}
@Override
public void execute(CommandSource var1, String[] var2) {
if(var2.length == 1) {
Optional<Player> player = EaglerXVelocity.proxy().getPlayer(var2[0]);
if(player.isPresent()) {
EaglerPlayerData playerData = EaglerPipeline.getEaglerHandle(player.get());
if(playerData != null) {
var1.sendMessage(Component.text("Eagler Client Brand: ", NamedTextColor.BLUE).append(Component.text(playerData.getEaglerBrandString(), NamedTextColor.WHITE)));
var1.sendMessage(Component.text("Eagler Client Version: ", NamedTextColor.BLUE).append(Component.text(playerData.getEaglerVersionString(), NamedTextColor.WHITE)));
var1.sendMessage(Component.text("Eagler Client UUID: ", NamedTextColor.BLUE).append(Component.text(playerData.getClientBrandUUID().toString(), NamedTextColor.WHITE)));
var1.sendMessage(Component.text("Minecraft Client Brand: ", NamedTextColor.BLUE).append(Component.text(player.get().getClientBrand(), NamedTextColor.WHITE)));
}else {
var1.sendMessage(Component.text("That player is not using eaglercraft!", NamedTextColor.RED));
}
}else {
var1.sendMessage(Component.text("That player was not found!", NamedTextColor.RED));
}
return;
}
if(var2.length == 2) {
Optional<Player> player = EaglerXVelocity.proxy().getPlayer(var2[1]);
if(player.isPresent()) {
EaglerPlayerData playerData = EaglerPipeline.getEaglerHandle(player.get());
if(playerData != null) {
if("uuid".equalsIgnoreCase(var2[0])) {
var1.sendMessage(Component.text("Eagler Client UUID: ", NamedTextColor.BLUE).append(Component.text(playerData.getClientBrandUUID().toString(), NamedTextColor.WHITE)));
return;
}else if("name".equalsIgnoreCase(var2[0])) {
var1.sendMessage(Component.text("Eagler Client Brand: ", NamedTextColor.BLUE).append(Component.text(playerData.getEaglerBrandString(), NamedTextColor.WHITE)));
var1.sendMessage(Component.text("Eagler Client Version: ", NamedTextColor.BLUE).append(Component.text(playerData.getEaglerVersionString(), NamedTextColor.WHITE)));
return;
}else if("mc".equalsIgnoreCase(var2[0])) {
var1.sendMessage(Component.text("Minecraft Client Brand: ", NamedTextColor.BLUE).append(Component.text(player.get().getClientBrand(), NamedTextColor.WHITE)));
return;
}
}else {
var1.sendMessage(Component.text("That player is not using eaglercraft!", NamedTextColor.RED));
return;
}
}else {
var1.sendMessage(Component.text("That player was not found!", NamedTextColor.RED));
return;
}
}
var1.sendMessage(Component.text("Usage: /client-brand [uuid|name|mc] <username>", NamedTextColor.RED));
}
}

View File

@ -47,8 +47,9 @@ public class CommandDomain extends EaglerCommand {
var1.sendMessage(Component.text("That user is not using Eaglercraft", NamedTextColor.RED));
return;
}
if(eagPlayer.origin != null) {
var1.sendMessage(Component.text("Domain of " + var2[0] + " is '" + eagPlayer.origin + "'", NamedTextColor.BLUE));
String origin = eagPlayer.getOrigin();
if(origin != null) {
var1.sendMessage(Component.text("Domain of " + var2[0] + " is '" + origin + "'", NamedTextColor.BLUE));
}else {
var1.sendMessage(Component.text("That user's browser did not send an origin header", NamedTextColor.RED));
}

View File

@ -69,8 +69,14 @@ public class EaglerListenerConfig {
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;
@ -97,8 +103,8 @@ public class EaglerListenerConfig {
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);
@ -146,7 +152,8 @@ public class EaglerListenerConfig {
cacheTrending, cachePortfolios);
return new EaglerListenerConfig(hostv4, hostv6, maxPlayer,
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;
@ -159,6 +166,9 @@ public class EaglerListenerConfig {
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;
@ -170,9 +180,9 @@ public class EaglerListenerConfig {
private final EaglerRateLimiter ratelimitQuery;
public EaglerListenerConfig(InetSocketAddress address, InetSocketAddress addressV6, int maxPlayer,
boolean forwardIp,
String forwardIpHeader, String redirectLegacyClientsTo, String serverIcon, List<String> serverMOTD,
boolean allowMOTD, boolean allowQuery, MOTDCacheConfiguration motdCacheConfig, HttpWebServer webServer,
boolean forwardIp, String forwardIpHeader, String redirectLegacyClientsTo, String serverIcon,
List<String> serverMOTD, boolean allowMOTD, boolean allowQuery, boolean allowV3, boolean allowV4,
int defragSendDelay, MOTDCacheConfiguration motdCacheConfig, HttpWebServer webServer,
boolean enableVoiceChat, EaglerRateLimiter ratelimitIp, EaglerRateLimiter ratelimitLogin,
EaglerRateLimiter ratelimitMOTD, EaglerRateLimiter ratelimitQuery) {
this.address = address;
@ -185,6 +195,9 @@ public class EaglerListenerConfig {
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;
@ -247,7 +260,19 @@ public class EaglerListenerConfig {
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_velocity.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 it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.bungee.Configuration;
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;
/**
* 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()) {
EaglerXVelocityAPIHelper.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<>();
Int2ObjectMap<PacketImageData> imageDumbHashTable = new Int2ObjectOpenHashMap<>();
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 = EaglerXVelocityAPIHelper.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 = EaglerXVelocityAPIHelper.loadFileToStringServerInfo(new File(baseDir, infoButtonEmbedFile));
if(infoButtonEnableTemplateMacros) {
rawData = EaglerXVelocityAPIHelper.loadServerInfoTemplateEagler(rawData, baseDir, infoButtonAllowTemplateEvalMacro);
}
serverInfoChunks = EaglerXVelocityAPIHelper.convertServerInfoToChunks(rawData.getBytes(StandardCharsets.UTF_8), hash, infoChunkSize);
if(!serverInfoChunks.isEmpty()) {
SPacketServerInfoDataChunkV4EAG pk = serverInfoChunks.get(0);
EaglerXVelocity.logger().info("Total server info embed size: {} bytes {}", pk.finalSize, 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();
EaglerXVelocity.logger().info("Total pause menu packet size: {} bytes", cnt);
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_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.bungee.Configuration;
/**
@ -95,7 +96,7 @@ public class EaglerRateLimiter {
protected long cooldownTimestamp = 0l;
protected RateLimitStatus rateLimit() {
long millis = System.currentTimeMillis();
long millis = EaglerXVelocityAPIHelper.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 = EaglerXVelocityAPIHelper.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

@ -3,10 +3,13 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
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;
@ -27,6 +30,7 @@ import com.google.gson.JsonSyntaxException;
import com.velocitypowered.api.util.GameProfile.Property;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.bungee.Configuration;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.bungee.ConfigurationProvider;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.bungee.YamlConfiguration;
@ -50,7 +54,7 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.web.HttpCont
public class EaglerVelocityConfig {
public static EaglerVelocityConfig 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 EaglerVelocityConfig {
Configuration configYml = prov.load(getConfigFile(directory, "settings.yml"));
String serverName = configYml.getString("server_name", "EaglercraftXVelocity Server");
EaglerXVelocityAPIHelper.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 EaglerVelocityConfig {
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 EaglerVelocityConfig {
}
}
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 = EaglerVelocityConfig.class.getResourceAsStream("default_pause_menu.yml")) {
copyConfigFile(is, pauseMenuYml);
}
File f2 = new File(pauseMenuFolder, "server_info.html");
if(!f2.isFile()) {
try(InputStream is = EaglerVelocityConfig.class.getResourceAsStream("default_pause_menu_server_info.html")) {
copyConfigFile(is, f2);
}
}
f2 = new File(pauseMenuFolder, "test_image.png");
if(!f2.isFile()) {
try(InputStream is = EaglerVelocityConfig.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 = EaglerVelocityConfig.class.getResourceAsStream("default_message_api_example.html")) {
copyConfigFile(is, f2);
}
}
f2 = new File(pauseMenuFolder, "message_api_v1.js");
if(!f2.isFile()) {
try(InputStream is = EaglerVelocityConfig.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 EaglerVelocityConfig {
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 EaglerVelocityConfig ret = new EaglerVelocityConfig(serverName, serverUUID, websocketKeepAliveTimeout,
websocketHandshakeTimeout, builtinHttpServerTimeout, websocketCompressionLevel, serverListeners,
@ -154,7 +197,7 @@ public class EaglerVelocityConfig {
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);
@ -181,6 +224,26 @@ public class EaglerVelocityConfig {
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 EaglerVelocityConfig {
EaglerXVelocity.logger().warn("MIME type '{}' defines no extensions!", mime);
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());
}
@ -211,13 +274,13 @@ public class EaglerVelocityConfig {
contentTypes.put(s, typeObj);
}
}catch(Throwable t) {
EaglerXVelocity.logger().warn("Exception parsing MIME type '{}' - {}", mime, t.toString());
EaglerXVelocity.logger().warn("Exception parsing MIME type '{}' - {}", mime, t);
}
}
}
private static Collection<String> loadICEServers(Configuration config) {
Collection<String> ret = new ArrayList(config.getList("voice_stun_servers"));
Collection<String> ret = new ArrayList<>((Collection<String>)config.getList("voice_stun_servers"));
Configuration turnServers = config.getSection("voice_turn_servers");
Iterator<String> turnItr = turnServers.getKeys().iterator();
while(turnItr.hasNext()) {
@ -228,18 +291,9 @@ public class EaglerVelocityConfig {
return ret;
}
@SuppressWarnings("deprecation")
private static JsonObject parseJsonObject(InputStream file) throws IOException {
StringBuilder str = new StringBuilder();
byte[] buffer = new byte[8192];
int i;
while((i = file.read(buffer)) > 0) {
str.append(new String(buffer, 0, i, "UTF-8"));
}
try {
return JsonParser.parseString(str.toString()).getAsJsonObject();
return JsonParser.parseReader(new InputStreamReader(file, StandardCharsets.UTF_8)).getAsJsonObject();
}catch(JsonSyntaxException ex) {
throw new IOException("Invalid JSONObject", ex);
}
@ -279,6 +333,8 @@ public class EaglerVelocityConfig {
private final boolean disableFNAWSkinsEverywhere;
private final Set<String> disableFNAWSkinsOnServers;
private boolean isCrackedFlag;
private final boolean enableBackendRPCAPI;
private final EaglerPauseMenuConfig pauseMenuConf;
Property[] eaglerPlayersVanillaSkinCached = new Property[] { isEaglerProperty };
public String getServerName() {
@ -444,6 +500,14 @@ public class EaglerVelocityConfig {
return disableFNAWSkinsOnServers;
}
public boolean getEnableBackendRPCAPI() {
return enableBackendRPCAPI;
}
public EaglerPauseMenuConfig getPauseMenuConf() {
return pauseMenuConf;
}
private EaglerVelocityConfig(String serverName, UUID serverUUID, long websocketKeepAliveTimeout,
long websocketHandshakeTimeout, long builtinHttpServerTimeout, int httpWebsocketCompressionLevel,
Map<String, EaglerListenerConfig> serverListeners, Map<String, HttpContentType> contentTypes,
@ -453,7 +517,8 @@ public class EaglerVelocityConfig {
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;
@ -485,6 +550,8 @@ public class EaglerVelocityConfig {
this.disableVoiceOnServers = disableVoiceOnServers;
this.disableFNAWSkinsEverywhere = disableFNAWSkinsEverywhere;
this.disableFNAWSkinsOnServers = disableFNAWSkinsOnServers;
this.enableBackendRPCAPI = enableBackendRPCAPI;
this.pauseMenuConf = pauseMenuConf;
}
}

View File

@ -0,0 +1,258 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import com.google.common.html.HtmlEscapers;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.translation.GlobalTranslator;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.repackage.lang3.StrTokenizer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.Base64;
/**
* 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(EaglerXVelocityAPIHelper.loadFileToByteArrayServerInfo(new File(state.baseDir, strs[2])));
case "text":
return escapeMacroResult(EaglerXVelocityAPIHelper.loadFileToStringServerInfo(new File(state.baseDir, strs[2])), state);
case "eval":
if(state.evalAllowed) {
return escapeMacroResult(loadTemplate(EaglerXVelocityAPIHelper.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);
TextComponent[] additionalArgs = new TextComponent[strs.length - 2];
for(int i = 0; i < additionalArgs.length; ++i) {
additionalArgs[i] = Component.text(strs[i + 2]);
}
return escapeMacroResult(LegacyComponentSerializer.legacySection().serialize(
GlobalTranslator.render(Component.translatable(strs[1]).arguments(Arrays.asList(additionalArgs)), Locale.getDefault())), 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

@ -0,0 +1,31 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config;
import java.io.IOException;
import java.io.OutputStream;
/**
* 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 TestOutputStream extends OutputStream {
@Override
public void write(int b) throws IOException {
}
@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_velocity.handlers;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;
@ -12,8 +11,9 @@ import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent.ForwardResult;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
@ -25,18 +25,18 @@ import com.velocitypowered.api.util.GameProfile.Property;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import net.kyori.adventure.text.Component;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.EaglerBackendRPCProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.auth.DefaultAuthSystem;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerAuthConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.backend_rpc_protocol.BackendRPCSessionHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.protocol.GameProtocolMessageController;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.Base64;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.CapePackets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.CapeServiceOffline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.SkinPackets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.SkinService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.voice.VoiceSignalPackets;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketEnableFNAWSkinsEAG;
/**
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
@ -55,9 +55,10 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.voice.VoiceSignalPa
*/
public class EaglerPacketEventListener {
public static final ChannelIdentifier FNAW_SKIN_ENABLE_CHANNEL = new LegacyChannelIdentifier("EAG|FNAWSEn-1.8");
public static final ChannelIdentifier GET_DOMAIN_CHANNEL = new LegacyChannelIdentifier("EAG|GetDomain");
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
public final EaglerXVelocity plugin;
public EaglerPacketEventListener(EaglerXVelocity plugin) {
@ -66,46 +67,51 @@ public class EaglerPacketEventListener {
@Subscribe(order = PostOrder.FIRST)
public void onPluginMessage(final PluginMessageEvent event) {
ChannelIdentifier tagObj = event.getIdentifier();
if(!(tagObj instanceof LegacyChannelIdentifier)) {
return;
}
String tag = tagObj.getId();
if(event.getSource() instanceof ConnectedPlayer) {
final ConnectedPlayer player = (ConnectedPlayer)event.getSource();
EaglerPlayerData eagPlayer = EaglerPipeline.getEaglerHandle(player);
if(eagPlayer != null) {
if(SkinService.CHANNEL.equals(event.getIdentifier())) {
EaglerXVelocity.proxy().getScheduler().buildTask(plugin, new Runnable() {
@Override
public void run() {
try {
SkinPackets.processPacket(event.getData(), player, plugin.getSkinService());
} catch (IOException e) {
player.disconnect(Component.text("Skin packet error!"));
EaglerXVelocity.logger().error("Eagler user \"{}\" raised an exception handling skins!", player.getUsername(), e);
}
}
}).schedule();
}else if(CapeServiceOffline.CHANNEL.equals(event.getIdentifier())) {
GameProtocolMessageController msgController = eagPlayer.getEaglerMessageController();
if(msgController != null) {
try {
CapePackets.processPacket(event.getData(), player, plugin.getCapeService());
} catch (IOException e) {
player.disconnect(Component.text("Cape packet error!"));
EaglerXVelocity.logger().error("Eagler user \"{}\" raised an exception handling capes!", player.getUsername(), e);
}
}else if(VoiceService.CHANNEL.equals(event.getIdentifier())) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && eagPlayer.getEaglerListenerConfig().getEnableVoiceChat()) {
try {
VoiceSignalPackets.processPacket(event.getData(), player, svc);
} catch (IOException e) {
player.disconnect(Component.text("Voice signal packet error!"));
EaglerXVelocity.logger().error("Eagler user \"{}\" raised an exception handling voice signals!", player.getUsername(), e);
if(msgController.handlePacket(tag, event.getData())) {
event.setResult(ForwardResult.handled());
return;
}
} catch (Throwable e) {
player.disconnect(Component.text("Eaglercraft packet error!"));
event.setResult(ForwardResult.handled());
return;
}
}
if(EaglerBackendRPCProtocol.CHANNEL_NAME.equals(tag)) {
player.disconnect(Component.text("Nope!"));
event.setResult(ForwardResult.handled());
return;
}
if(EaglerBackendRPCProtocol.CHANNEL_NAME_READY.equals(tag)) {
event.setResult(ForwardResult.handled());
return;
}
}
}else if(event.getSource() instanceof ServerConnection && event.getTarget() instanceof ConnectedPlayer) {
ConnectedPlayer player = (ConnectedPlayer)event.getTarget();
if(GET_DOMAIN_CHANNEL.equals(event.getIdentifier())) {
EaglerPlayerData eagPlayerData = EaglerPipeline.getEaglerHandle(player);
if(eagPlayerData != null) {
EaglerPlayerData eagPlayerData = EaglerPipeline.getEaglerHandle(player);
if(eagPlayerData != null) {
if(EaglerBackendRPCProtocol.CHANNEL_NAME.equals(tag)) {
event.setResult(ForwardResult.handled());
try {
eagPlayerData.handleBackendRPCPacket((ServerConnection)event.getSource(), event.getData());
}catch(Throwable t) {
EaglerXVelocity.logger().error("[{}]: Caught an exception handling backend RPC packet!", player.getUsername(), t);
}
}else if("EAG|GetDomain".equals(tag)) {
event.setResult(ForwardResult.handled());
String domain = eagPlayerData.getOrigin();
if(domain == null) {
((ServerConnection)event.getSource()).sendPluginMessage(GET_DOMAIN_CHANNEL, new byte[] { 0 });
@ -113,6 +119,15 @@ public class EaglerPacketEventListener {
((ServerConnection)event.getSource()).sendPluginMessage(GET_DOMAIN_CHANNEL, domain.getBytes(StandardCharsets.UTF_8));
}
}
}else {
if(EaglerBackendRPCProtocol.CHANNEL_NAME.equals(tag)) {
event.setResult(ForwardResult.handled());
try {
BackendRPCSessionHandler.handlePacketOnVanilla((ServerConnection)event.getSource(), player, event.getData());
}catch(Throwable t) {
EaglerXVelocity.logger().error("[{}]: Caught an exception handling backend RPC packet!", player.getUsername(), t);
}
}
}
}
}
@ -165,28 +180,32 @@ public class EaglerPacketEventListener {
ConnectedPlayer player = (ConnectedPlayer)event.getPlayer();
EaglerPlayerData eagData = EaglerPipeline.getEaglerHandle(player);
if(eagData != null && eagData.getEaglerListenerConfig().getEnableVoiceChat()) {
plugin.getVoiceService().handlePlayerLoggedOut(player);
plugin.getVoiceService().handlePlayerLoggedOut(eagData);
}
}
}
@Subscribe
public void onServerConnected(ServerConnectedEvent event) {
if(event.getPlayer() instanceof ConnectedPlayer) {
ConnectedPlayer player = (ConnectedPlayer)event.getPlayer();
EaglerPlayerData eagData = EaglerPipeline.getEaglerHandle(player);
if(eagData != null) {
ServerInfo sv = event.getServer().getServerInfo();
public void onServerConnected(ServerPostConnectEvent event) {
try {
ConnectedPlayer player = (ConnectedPlayer) event.getPlayer();
ServerConnection server = player.getConnectedServer();
BackendRPCSessionHandler.sendPluginMessage(server, EaglerBackendRPCProtocol.CHANNEL_NAME_READY, EMPTY_BYTE_ARRAY);
EaglerPlayerData playerObj = EaglerPipeline.getEaglerHandle(player);
if(playerObj != null) {
ServerInfo sv = server.getServerInfo();
boolean fnawSkins = !plugin.getConfig().getDisableFNAWSkinsEverywhere()
&& !plugin.getConfig().getDisableFNAWSkinsOnServersSet().contains(sv.getName());
if(fnawSkins != eagData.currentFNAWSkinEnableStatus) {
eagData.currentFNAWSkinEnableStatus = fnawSkins;
player.sendPluginMessage(FNAW_SKIN_ENABLE_CHANNEL, new byte[] { fnawSkins ? (byte)1 : (byte)0 });
if(fnawSkins != playerObj.currentFNAWSkinEnableStatus.getAndSet(fnawSkins)) {
playerObj.sendEaglerMessage(new SPacketEnableFNAWSkinsEAG(fnawSkins, false));
}
if(eagData.getEaglerListenerConfig().getEnableVoiceChat()) {
plugin.getVoiceService().handleServerConnected(player, sv);
if(playerObj.getEaglerListenerConfig().getEnableVoiceChat()) {
plugin.getVoiceService().handleServerConnected(playerObj, sv);
}
}
}catch(Throwable t) {
EaglerXVelocity.logger().error("Failed to process server connection ready handler for player \"{}\"",
event.getPlayer().getUsername(), t);
}
}
@ -195,9 +214,16 @@ public class EaglerPacketEventListener {
if(event.getPreviousServer() != null && (event.getPlayer() instanceof ConnectedPlayer)) {
ConnectedPlayer player = (ConnectedPlayer)event.getPlayer();
EaglerPlayerData eagData = EaglerPipeline.getEaglerHandle(player);
if(eagData != null && eagData.getEaglerListenerConfig().getEnableVoiceChat()) {
plugin.getVoiceService().handleServerDisconnected(player, event.getPreviousServer().getServerInfo());
if(eagData != null) {
BackendRPCSessionHandler rpcHandler = eagData.getRPCSessionHandler();
if(rpcHandler != null) {
rpcHandler.handleConnectionLost();
}
if(eagData.getEaglerListenerConfig().getEnableVoiceChat()) {
plugin.getVoiceService().handleServerDisconnected(eagData, event.getPreviousServer().getServerInfo());
}
}
}
}
}

View File

@ -0,0 +1,437 @@
/*
* 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_velocity.repackage.lang3;
import java.util.Arrays;
/**
* 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 (chars == null || chars.length() == 0) {
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 (str == null || str.length() == 0) {
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 = chars.clone();
Arrays.sort(this.chars);
}
/**
* 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

@ -3,6 +3,7 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import io.netty.channel.Channel;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
/**
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
@ -35,11 +36,12 @@ public class EaglerConnectionInstance {
public ConnectedPlayer userConnection = null;
public EaglerPlayerData eaglerData = null;
public HttpServerQueryHandler queryHandler = null;
public EaglerConnectionInstance(Channel channel) {
this.channel = channel;
this.creationTime = this.lastServerPingPacket = this.lastClientPingPacket =
this.lastClientPongPacket = System.currentTimeMillis();
this.lastClientPongPacket = EaglerXVelocityAPIHelper.steadyTimeMillis();
}
}

View File

@ -6,6 +6,7 @@ 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 net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import java.util.List;
@ -31,7 +32,7 @@ public class EaglerMinecraftDecoder extends MessageToMessageDecoder<WebSocketFra
return;
}
EaglerConnectionInstance con = ctx.channel().attr(EaglerPipeline.CONNECTION_INSTANCE).get();
long millis = System.currentTimeMillis();
long millis = EaglerXVelocityAPIHelper.steadyTimeMillis();
if(frame instanceof BinaryWebSocketFrame) {
out.add(frame.content().retain());
}else if(frame instanceof PingWebSocketFrame) {

View File

@ -8,13 +8,13 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.scheduler.VelocityScheduler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
@ -29,10 +29,13 @@ import io.netty.handler.codec.http.websocketx.extensions.compression.DeflateFram
import io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker;
import io.netty.util.AttributeKey;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerVelocityConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData.ClientCertificateHolder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.web.HttpWebServer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketUpdateCertEAG;
/**
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
@ -58,10 +61,9 @@ 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 Collection<Channel> openChannels = new LinkedList();
public static final ChannelIdentifier UPDATE_CERT_CHANNEL = new LegacyChannelIdentifier("EAG|UpdateCert-1.8");
public static final Collection<Channel> openChannels = new LinkedList<>();
public static final TimerTask closeInactive = new TimerTask() {
@ -75,7 +77,7 @@ public class EaglerPipeline {
long httpTimeout = conf.getBuiltinHttpServerTimeout();
List<Channel> channelsList;
synchronized(openChannels) {
long millis = System.currentTimeMillis();
long millis = EaglerXVelocityAPIHelper.steadyTimeMillis();
Iterator<Channel> channelIterator = openChannels.iterator();
while(channelIterator.hasNext()) {
Channel c = channelIterator.next();
@ -83,7 +85,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();
@ -103,7 +113,7 @@ public class EaglerPipeline {
}
}
}
channelsList = new ArrayList(openChannels);
channelsList = new ArrayList<>(openChannels);
}
for(EaglerListenerConfig lst : conf.getServerListeners()) {
HttpWebServer srv = lst.getWebServer();
@ -111,47 +121,88 @@ public class EaglerPipeline {
try {
srv.flushCache();
}catch(Throwable t) {
log.error("Failed to flush web server cache for: {}", lst.getAddress().toString());
log.error("Failed to flush web server cache for: {}", lst.getAddress());
t.printStackTrace();
}
}
}
if(!conf.getUpdateConfig().isBlockAllClientUpdates()) {
int sizeTracker = 0;
for(Channel c : channelsList) {
EaglerConnectionInstance conn = c.attr(EaglerPipeline.CONNECTION_INSTANCE).get();
if(conn.userConnection == null) {
continue;
}
EaglerPlayerData i = conn.eaglerData;
ClientCertificateHolder certHolder = null;
final int serverInfoSendRate = Math.max(conf.getPauseMenuConf().getInfoSendRate(), 1);
boolean blockAllClientUpdates = conf.getUpdateConfig().isBlockAllClientUpdates();
final AtomicInteger sizeTracker = blockAllClientUpdates ? null : new AtomicInteger(0);
final int rateLimitParam = conf.getUpdateConfig().getCertPacketDataRateLimit() / 4;
VelocityScheduler sched = EaglerXVelocity.proxy().getScheduler();
EaglerXVelocity plugin = EaglerXVelocity.getEagler();
for(Channel c : channelsList) {
final EaglerConnectionInstance conn = c.attr(EaglerPipeline.CONNECTION_INSTANCE).get();
if(conn.userConnection == null) {
continue;
}
final EaglerPlayerData i = conn.eaglerData;
boolean certToSend = false;
if(!blockAllClientUpdates) {
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.sendPluginMessage(UPDATE_CERT_CHANNEL, certHolder.data);
sizeTracker += certHolder.data.length;
if(sizeTracker > (conf.getUpdateConfig().getCertPacketDataRateLimit() / 4)) {
break;
}
if(!i.certificatesToSend.isEmpty()) {
certToSend = true;
}
}
}
EaglerUpdateSvc.updateTick();
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.buildTask(plugin, () -> {
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) {
conn.eaglerData.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.error("Exception in thread \"{}\"!", Thread.currentThread().getName(), t);
}
}
}
}
}).schedule();
}
}
}catch(Throwable t) {
log.error("Exception in thread \"{}\"! {}", Thread.currentThread().getName(), t.toString());
t.printStackTrace();
log.error("Exception in thread \"{}\"!", Thread.currentThread().getName(), t);
}
}
};

View File

@ -1,9 +1,41 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.collect.Collections2;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import net.kyori.adventure.text.Component;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EnumWebViewState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.NotificationBadgeBuilder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftVoiceStatusChangeEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerVelocityConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.backend_rpc_protocol.BackendRPCSessionHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.backend_rpc_protocol.EnumSubscribedEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.protocol.GameProtocolMessageController;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.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;
/**
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
@ -31,6 +63,15 @@ public class EaglerPlayerData {
}
}
protected final EaglerConnectionInstance connInstance;
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 InetSocketAddress eaglerAddress;
public final SimpleRateLimiter skinLookupRateLimiter;
public final SimpleRateLimiter skinUUIDLookupRateLimiter;
public final SimpleRateLimiter skinTextureDownloadRateLimiter;
@ -38,32 +79,387 @@ public class EaglerPlayerData {
public final SimpleRateLimiter voiceConnectRateLimiter;
public final EaglerListenerConfig listener;
public final String origin;
protected final String userAgent;
public final ClientCertificateHolder clientCertificate;
public final Set<ClientCertificateHolder> certificatesToSend;
public final Set<Integer> certificatesSent;
public boolean currentFNAWSkinEnableStatus = true;
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 GameProfile gameProfile;
protected final AtomicReference<EaglercraftVoiceStatusChangeEvent.EnumVoiceState> lastVoiceState = new AtomicReference<>(
EaglercraftVoiceStatusChangeEvent.EnumVoiceState.SERVER_DISABLE);
public EaglerPlayerData(EaglerListenerConfig listener, String origin, ClientCertificateHolder clientCertificate) {
public EaglerPlayerData(EaglerConnectionInstance connInstance, EaglerListenerConfig listener,
int clientProtocolVersion, int gameProtocolVersion, String clientBrandString, String clientVersionString,
UUID clientBrandUUID, String username, UUID playerUUID, InetSocketAddress eaglerAddress, String origin,
String userAgent, ClientCertificateHolder clientCertificate, boolean allowCookie, byte[] cookie,
Map<String, byte[]> otherProfileData, GameProfile gameProfile) {
this.connInstance = connInstance;
this.listener = listener;
this.clientProtocolVersion = clientProtocolVersion;
this.gameProtocolVersion = gameProtocolVersion;
this.clientBrandString = clientBrandString;
this.clientVersionString = clientVersionString;
this.clientBrandUUID = clientBrandUUID;
this.username = username;
this.playerUUID = playerUUID;
this.eaglerAddress = eaglerAddress;
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.certificatesSent = new HashSet();
this.gameProfile = gameProfile;
this.certificatesToSend = new HashSet<>();
this.certificatesSent = new HashSet<>();
EaglerVelocityConfig conf = EaglerXVelocity.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());
}
}
public InetSocketAddress getSocketAddress() {
return eaglerAddress;
}
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) {
connInstance.userConnection.disconnect(Component.text("Failed to write eaglercraft packet! (" + e.toString() + ")"));
}
}else {
throw new IllegalStateException("Race condition detected, messageProtocolController is null!");
}
}
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 {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to send a webview message to player \"{}\", but the player doesn't have a webview message channel open!",
getSocketAddress(), username);
}
}
public void sendWebViewMessage(String str) {
if(webViewMessageChannelOpen.get()) {
sendEaglerMessage(new SPacketWebViewMessageV4EAG(str));
}else {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to send a webview message to player \"{}\", but the player doesn't have a webview message channel open!",
getSocketAddress(), username);
}
}
public void sendWebViewMessage(byte[] bin) {
if(webViewMessageChannelOpen.get()) {
sendEaglerMessage(new SPacketWebViewMessageV4EAG(bin));
}else {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to send a webview message to player \"{}\", but the player doesn't have a webview message channel open!",
getSocketAddress(), username);
}
}
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 {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to set a cookie for player \"{}\", but the player has cookies disabled!",
getSocketAddress(), username);
}
}
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 {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to register notification icons for player \"{}\", but the player has notifications disabled!",
getSocketAddress(), username);
}
}
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 {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to register notification icons for player \"{}\", but the player has notifications disabled!",
getSocketAddress(), username);
}
}
public void showNotificationBadge(NotificationBadgeBuilder badgeBuilder) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(badgeBuilder.buildPacket());
}else {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to show notification badges to player \"{}\", but the player has notifications disabled!",
getSocketAddress(), username);
}
}
public void showNotificationBadge(SPacketNotifBadgeShowV4EAG badgePacket) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(badgePacket);
}else {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to show notification badges to player \"{}\", but the player has notifications disabled!",
getSocketAddress(), username);
}
}
public void hideNotificationBadge(UUID badgeUUID) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifBadgeHideV4EAG(badgeUUID.getMostSignificantBits(), badgeUUID.getLeastSignificantBits()));
}else {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to hide notification badges for player \"{}\", but the player has notifications disabled!",
getSocketAddress(), username);
}
}
public void releaseNotificationIcon(UUID uuid) {
if(clientProtocolVersion >= 4) {
sendEaglerMessage(new SPacketNotifIconsReleaseV4EAG(
Arrays.asList(new SPacketNotifIconsReleaseV4EAG.DestroyIcon(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits()))));
}else {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to release notification icons for player \"{}\", but the player has notifications disabled!",
getSocketAddress(), username);
}
}
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 {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to release notification icons for player \"{}\", but the player has notifications disabled!",
getSocketAddress(), username);
}
}
public boolean redirectToWebSocketSupported() {
return clientProtocolVersion >= 4;
}
public void redirectPlayerToWebSocket(String serverAddress) {
if(getEaglerProtocol().ver >= 4) {
sendEaglerMessage(new SPacketRedirectClientV4EAG(serverAddress));
}else {
EaglerXVelocity.logger().warn(
"[{}]: Some plugin tried to redirect player \"{}\" to a different websocket, but that player's client doesn't support this feature!",
getSocketAddress(), username);
}
}
public BackendRPCSessionHandler getRPCSessionHandler() {
return backedRPCSessionHandler;
}
public boolean getRPCEventSubscribed(EnumSubscribedEvent event) {
return backedRPCSessionHandler != null && backedRPCSessionHandler.isSubscribed(event);
}
public void handleBackendRPCPacket(ServerConnection server, byte[] data) {
if(backedRPCSessionHandler != null) {
backedRPCSessionHandler.handleRPCPacket(server, data);
}else {
EaglerXVelocity.logger().error(
"[{}]: Server tried to send backend RPC packet to player \"{}\" but this feature is not enabled. Enable it by setting \"enable_backend_rpc_api: true\" in settings.yml",
getSocketAddress(), username);
}
}
public void fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState state) {
EaglercraftVoiceStatusChangeEvent.EnumVoiceState oldState = lastVoiceState.getAndSet(state);
if(state != oldState) {
EaglerXVelocity.proxy().getEventManager().fireAndForget(
new EaglercraftVoiceStatusChangeEvent(getPlayerObj(), listener, this, oldState, state));
}
}
public String getEaglerBrandString() {
return clientBrandString;
}
public String getEaglerVersionString() {
return clientVersionString;
}
public UUID getClientBrandUUID() {
return clientBrandUUID;
}
public byte[] getOtherProfileDataFromHandshake(String name) {
return otherProfileDataFromHanshake.get(name);
}
public String getOrigin() {
return origin;
}
public String getUserAgent() {
return userAgent;
}
public EaglerListenerConfig getEaglerListenerConfig() {
return listener;
}
public String getName() {
return username;
}
public UUID getUniqueId() {
return playerUUID;
}
public ConnectedPlayer getPlayerObj() {
return connInstance.userConnection;
}
public VelocityServerConnection getConnectedServer() {
ConnectedPlayer conn = connInstance.userConnection;
return conn != null ? conn.getConnectedServer() : null;
}
public boolean isOnlineMode() {
ConnectedPlayer conn = connInstance.userConnection;
return conn != null && conn.isOnlineMode();
}
public GameProfile getGameProfile() {
return gameProfile;
}
}

View File

@ -24,6 +24,7 @@ import com.velocitypowered.api.proxy.Player;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocityVersion;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.auth.SHA1Digest;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerUpdateConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData.ClientCertificateHolder;
@ -45,8 +46,8 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayer
*/
public class EaglerUpdateSvc {
private static final List<ClientCertificateHolder> certs = new ArrayList();
private static final Map<String,CachedClientCertificate> certsCache = new HashMap();
private static final List<ClientCertificateHolder> certs = new ArrayList<>();
private static final Map<String,CachedClientCertificate> certsCache = new HashMap<>();
private static class CachedClientCertificate {
private final ClientCertificateHolder cert;
@ -62,7 +63,7 @@ public class EaglerUpdateSvc {
public static void updateTick() {
Logger log = EaglerXVelocity.logger();
long millis = System.currentTimeMillis();
long millis = EaglerXVelocityAPIHelper.steadyTimeMillis();
EaglerUpdateConfig conf = EaglerXVelocity.getEagler().getConfig().getUpdateConfig();
if(conf.isDownloadLatestCerts() && millis - lastDownload > (long)conf.getCheckForUpdatesEvery() * 1000l) {
lastDownload = millis;
@ -73,7 +74,7 @@ public class EaglerUpdateSvc {
log.error("Uncaught exception downloading certificates!");
t.printStackTrace();
}
millis = System.currentTimeMillis();
millis = EaglerXVelocityAPIHelper.steadyTimeMillis();
}
if(conf.isEnableEagcertFolder() && millis - lastEnumerate > 5000l) {
lastEnumerate = millis;
@ -96,7 +97,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);
@ -176,7 +177,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

@ -21,6 +21,7 @@ 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_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftWebSocketOpenEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerRateLimiter;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.RateLimitStatus;
@ -68,12 +69,12 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
try {
ctx.channel().attr(EaglerPipeline.REAL_ADDRESS).set(InetAddress.getByName(rateLimitHost));
} catch (UnknownHostException ex) {
EaglerXVelocity.logger().warn("[" + ctx.channel().remoteAddress() + "]: Connected with an invalid '" + conf.getForwardIpHeader() + "' header, disconnecting...");
EaglerXVelocity.logger().warn("[{}]: Connected with an invalid '{}' header, disconnecting...", ctx.channel().remoteAddress(), conf.getForwardIpHeader());
ctx.close();
return;
}
} else {
EaglerXVelocity.logger().warn("[" + ctx.channel().remoteAddress() + "]: Connected without a '" + conf.getForwardIpHeader() + "' header, disconnecting...");
EaglerXVelocity.logger().warn("[{}]: Connected without a '{}' header, disconnecting...", ctx.channel().remoteAddress(), conf.getForwardIpHeader());
ctx.close();
return;
}
@ -103,10 +104,23 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
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 evt = new EaglercraftWebSocketOpenEvent(ctx.channel(), conf, rateLimitHost, origin, userAgent);
try {
evt = EaglerXVelocity.proxy().getEventManager().fire(evt).join();
}catch(Throwable t) {
ctx.close();
return;
}
if(evt.isCancelled()) {
ctx.close();
return;
}
ctx.channel().attr(EaglerPipeline.HOST).set(headers.get(HttpHeaderNames.HOST));
ctx.pipeline().replace(this, "HttpWebSocketHandler", new HttpWebSocketHandler(conf));
}
@ -172,7 +186,7 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (ctx.channel().isActive()) {
EaglerXVelocity.logger().warn("[Pre][" + ctx.channel().remoteAddress() + "]: Exception Caught: " + cause.toString(), cause);
EaglerXVelocity.logger().warn("[Pre][{}]: Exception Caught: {}", ctx.channel().remoteAddress(), cause.toString(), cause);
ctx.close();
}
}

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

@ -12,10 +12,12 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import com.google.common.collect.Sets;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@ -34,6 +36,8 @@ import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.GameProfile.Property;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.ConnectionType;
import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.*;
import com.velocitypowered.proxy.network.Connections;
@ -60,11 +64,14 @@ import io.netty.handler.timeout.WriteTimeoutHandler;
import io.netty.util.ReferenceCountUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.translation.GlobalTranslator;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocityVersion;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.JSONLegacySerializer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftClientBrandEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftHandleAuthCookieEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftHandleAuthPasswordEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftIsAuthRequiredEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftMOTDEvent;
@ -75,11 +82,14 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.Eaglercra
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.auth.DefaultAuthSystem;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.command.CommandConfirmCode;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.*;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.protocol.GameProtocolMessageController;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.query.MOTDQueryHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.query.QueryManager;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.CapePackets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.SkinPackets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins.SkinService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketCustomizePauseMenuV4EAG;
import static com.velocitypowered.proxy.network.Connections.*;
@ -143,16 +153,18 @@ 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 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;
@ -161,6 +173,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;
}
@ -218,7 +233,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if (i >= conf.getMaxPlayer()) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Proxy is full")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}
}
@ -293,12 +308,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);
@ -310,32 +325,37 @@ 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();
@ -350,34 +370,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
@ -390,14 +417,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();
@ -427,6 +454,19 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}
}
EaglercraftClientBrandEvent brandEvent = new EaglercraftClientBrandEvent(eaglerBrand, eaglerVersionString,
ctx.channel().attr(EaglerPipeline.ORIGIN).get(), clientProtocolVersion, addr);
brandEvent = eaglerXBungee.getProxy().getEventManager().fire(brandEvent).join();
if(brandEvent.isCancelled()) {
Component kickReason = brandEvent.getMessage();
if(kickReason == null) {
kickReason = Component.text("End of stream");
}
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, kickReason)
.addListener(ChannelFutureListener.CLOSE);
return;
}
final boolean final_useSnapshotFallbackProtocol = useSnapshotFallbackProtocol;
Runnable continueThread = () -> {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_VERSION;
@ -434,7 +474,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) {
@ -460,7 +500,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if(meth == -1) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Unsupported authentication method resolved")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
EaglerXVelocity.logger().error("[{}]: Disconnecting, unsupported AuthMethod: {}", localAddrString, authRequireEvent.getUseAuthType());
return;
}
@ -488,28 +528,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();
if(resp == null) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "IsAuthRequiredEvent was not handled")
.addListener(ChannelFutureListener.CLOSE);
EaglerXVelocity.logger().error("[{}]: Disconnecting, no installed authentication system handled: {}", localAddrString, authRequireEvent.toString());
.addListener(ChannelFutureListener.CLOSE);
EaglerXVelocity.logger().error("[{}]: Disconnecting, no installed authentication system handled: {}", localAddrString, authRequireEvent);
return;
}
if(resp == AuthResponse.DENY) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, authRequireEvent.getKickMessage())
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}
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);
EaglerXVelocity.logger().error("[{}]: Disconnecting, no authentication method provided by handler", localAddrString);
return;
}
@ -517,7 +557,7 @@ 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);
EaglerXVelocity.logger().error("[{}]: Disconnecting, unsupported AuthMethod: {}", localAddrString, type);
return;
}
@ -531,7 +571,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);
EaglerXVelocity.logger().error("[{}]: Disconnecting, no authentication method provided by handler", localAddrString);
return;
}
@ -568,24 +608,23 @@ 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);
.addListener(ChannelFutureListener.CLOSE);
return;
}
if(clientUsername.length() < 3) {
sendLoginDenied(ctx, "Username must be at least 3 characters")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}
if(clientUsername.length() > 16) {
sendLoginDenied(ctx, "Username must be under 16 characters")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
return;
}
@ -603,11 +642,28 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
clientUUID = UUID.nameUUIDFromBytes(uuidHashGenerator);
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");
}
@ -615,15 +671,14 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
Runnable continueThread = () -> {
final VelocityServer bungee = EaglerXVelocity.proxy();
String usernameStr = clientUsername.toString();
if (bungee.getPlayer(usernameStr).isPresent()) {
if (bungee.getPlayer(clientUsername).isPresent()) {
sendLoginDenied(ctx, LegacyComponentSerializer.legacySection().serialize(GlobalTranslator.render(Component.translatable("velocity.error.already-connected-proxy"), Locale.getDefault())))
.addListener(ChannelFutureListener.CLOSE);
.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);
@ -635,53 +690,112 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
EaglerXVelocity eaglerXBungee = EaglerXVelocity.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().getEventManager().fire(handleEvent).join();
}
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);
EaglerXVelocity.logger().error(
"[{}]: Disconnecting, no installed authentication system handled: {}",
localAddrString, 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);
EaglerXVelocity.logger().info("[{}]: Displaying authentication screen", localAddrString);
return;
}else {
handleEvent = eaglerXBungee.getProxy().getEventManager().fire(handleEvent).join();
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);
});
handleEvent = eaglerXBungee.getProxy().getEventManager().fire(handleEvent).join();
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 {
@ -691,35 +805,60 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}else {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
sendErrorWrong(ctx, op, "STATE_CLIENT_VERSION")
.addListener(ChannelFutureListener.CLOSE);
.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 {
@ -746,7 +885,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) {
@ -757,7 +896,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_INVALID_PACKET, op == -1 ?
"Invalid Packet" : "Invalid Packet #" + op)
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
}
}
@ -775,7 +914,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if (i >= conf.getMaxPlayer()) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Proxy is full")
.addListener(ChannelFutureListener.CLOSE);
.addListener(ChannelFutureListener.CLOSE);
connectionClosed = true;
return;
}
@ -794,6 +933,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if(addr != null) {
baseAddress = new InetSocketAddress(addr, baseAddress.getPort());
}
final InetSocketAddress final_baseAddress = baseAddress;
ProtocolVersion protocolVers = ProtocolVersion.getProtocolVersion(gameProtocolVersion);
if(!protocolVers.isSupported()) {
@ -820,6 +960,8 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
throw new RuntimeException(e);
}
con.setType(ConnectionTypes.VANILLA);
EaglerVelocityConfig eaglerConf = EaglerXVelocity.getEagler().getConfig();
EaglerUpdateConfig updateconf = eaglerConf.getUpdateConfig();
@ -833,6 +975,30 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}
final EaglerPlayerData.ClientCertificateHolder cert = mycert;
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(EaglerXVelocityAPIHelper.BRAND_NULL_UUID)
|| clientBrandUUID.equals(EaglerXVelocityAPIHelper.BRAND_PENDING_UUID)
|| clientBrandUUID.equals(EaglerXVelocityAPIHelper.BRAND_VANILLA_UUID)) {
clientBrandUUID = null;
}
}
}else {
clientBrandUUID = EaglerXVelocityAPIHelper.makeClientBrandUUIDLegacy(clientBrandAsString);
}
final UUID final_clientBrandUUID = clientBrandUUID;
final 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());
}
}
InitialInboundConnection inboundCon;
try {
inboundCon = stupidConstructor.newInstance(con, cleanVhost(hostName), fakeHandshake);
@ -873,8 +1039,8 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
Optional<Component> disconnectReason = result.getReasonComponent();
if (disconnectReason.isPresent()) {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE,
JSONComponentSerializer.json().serialize(GlobalTranslator.render(disconnectReason.get(), Locale.getDefault())))
.addListener(ChannelFutureListener.CLOSE);
GlobalTranslator.render(disconnectReason.get(), Locale.getDefault()))
.addListener(ChannelFutureListener.CLOSE);
return;
}
@ -897,7 +1063,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if (con.isClosed()) {
return CompletableFuture.completedFuture(null);
} else {
GameProfile gp = profileEvent.getGameProfile();
GameProfile gp = profileRequestEvent.getGameProfile();
if(eaglerConf.getEnableIsEaglerPlayerProperty()) {
gp = gp.addProperty(EaglerVelocityConfig.isEaglerProperty);
}
@ -918,28 +1084,31 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
return CompletableFuture.completedFuture(null);
} else {
boolean doRegisterSkins = true;
boolean doForceSkins = false;
EaglercraftRegisterSkinEvent registerSkinEvent = bungee.getEventManager().fire(new EaglercraftRegisterSkinEvent(usernameStr, clientUUID)).join();
EaglercraftRegisterSkinEvent registerSkinEvent = bungee.getEventManager()
.fire(new EaglercraftRegisterSkinEvent(usernameStr, clientUUID,
authRequireEvent != null ? authRequireEvent.getAuthAttachment() : null)).join();
Property prop = registerSkinEvent.getForceUseMojangProfileProperty();
boolean useExistingProp = registerSkinEvent.getForceUseLoginResultObjectTextures();
if(prop != null) {
texturesOverrideProperty = prop;
overrideEaglerToVanillaSkins = true;
if(clientProtocolVersion >= 4 && (EaglerXVelocity.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) {
EaglerXVelocity.getEagler().getSkinService().registerTextureToPlayerAssociation(customUrl, gp.getId());
doRegisterSkins = false;
overrideEaglerToVanillaSkins = false;
if(clientProtocolVersion >= 4) {
doForceSkins = true;
}
}
}
@ -947,11 +1116,13 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if(texturesOverrideProperty != null) {
gp = gp.addProperties(Arrays.asList(texturesOverrideProperty, EaglerVelocityConfig.isEaglerProperty));
player.setGameProfileProperties(gp.getProperties());
}else {
if(!useExistingProp) {
String vanillaSkin = eaglerConf.getEaglerPlayersVanillaSkin();
if(vanillaSkin != null) {
gp = gp.addProperties(Arrays.asList(eaglerConf.getEaglerPlayersVanillaSkinProperties()));
player.setGameProfileProperties(gp.getProperties());
}
}
}
@ -973,6 +1144,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
}
}
doRegisterSkins = false;
if(clientProtocolVersion >= 4) {
doForceSkins = true;
}
}catch(Throwable t) {
}
break;
@ -982,20 +1156,29 @@ 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_v1"),
EaglerXVelocity.getEagler().getSkinService());
SkinPackets.registerEaglerPlayer(clientUUID, profileData.get("skin_v2"),
EaglerXVelocity.getEagler().getSkinService(), 4);
} catch (Throwable ex) {
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXVelocity.getEagler().getSkinService());
EaglerXVelocity.logger().info("[" + ctx.channel().remoteAddress() + "]: Invalid skin packet: " + ex.toString());
EaglerXVelocity.logger().info("[{}]: Invalid skin packet: {}", ctx.channel().remoteAddress(), ex.toString());
}
}else if(profileData.containsKey("skin_v1")) {
try {
SkinPackets.registerEaglerPlayer(clientUUID, profileData.get("skin_v1"),
EaglerXVelocity.getEagler().getSkinService(), 3);
} catch (Throwable ex) {
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXVelocity.getEagler().getSkinService());
EaglerXVelocity.logger().info("[{}]: Invalid skin packet: {}", ctx.channel().remoteAddress(), ex.toString());
}
}else {
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXVelocity.getEagler().getSkinService());
}
}
EaglercraftRegisterCapeEvent registerCapeEvent = bungee.getEventManager().fire(new EaglercraftRegisterCapeEvent(usernameStr, clientUUID)).join();
EaglercraftRegisterCapeEvent registerCapeEvent = bungee.getEventManager().fire(new EaglercraftRegisterCapeEvent(usernameStr,
clientUUID, authRequireEvent != null ? authRequireEvent.getAuthAttachment() : null)).join();
byte[] forceCape = registerCapeEvent.getForceSetUseCustomPacket();
if(forceCape != null) {
@ -1008,18 +1191,44 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
EaglerXVelocity.getEagler().getCapeService());
} catch (Throwable ex) {
CapePackets.registerEaglerPlayerFallback(clientUUID, EaglerXVelocity.getEagler().getCapeService());
EaglerXVelocity.logger().info("[" + ctx.channel().remoteAddress() + "]: Invalid cape packet: " + ex.toString());
EaglerXVelocity.logger().info("[{}]: Invalid cape packet: {}", ctx.channel().remoteAddress(), ex.toString());
}
}else {
CapePackets.registerEaglerPlayerFallback(clientUUID, EaglerXVelocity.getEagler().getCapeService());
}
EaglerXVelocity.logger().info("{} has connected", player);
if(conf.getEnableVoiceChat()) {
EaglerXVelocity.getEagler().getVoiceService().handlePlayerLoggedIn(player);
EaglerConnectionInstance connInstance = ctx.channel().attr(EaglerPipeline.CONNECTION_INSTANCE).get();
EaglerPlayerData epd = connInstance.eaglerData = new EaglerPlayerData(connInstance,
conf, clientProtocolVersion, gameProtocolVersion, clientBrandString,
clientVersionString, final_clientBrandUUID, clientUsername, clientUUID,
final_baseAddress, ctx.channel().attr(EaglerPipeline.ORIGIN).get(),
ctx.channel().attr(EaglerPipeline.USER_AGENT).get(), cert,
clientEnableCookie, clientCookieData, otherProfileData, player.getGameProfile());
epd.messageProtocolController = new GameProtocolMessageController(player,
GamePluginMessageProtocol.getByVersion(clientProtocolVersion),
GameProtocolMessageController.createServerHandler(clientProtocolVersion, player,
epd, EaglerXVelocity.getEagler()), conf.getDefragSendDelay());
if(doForceSkins) {
EaglerXVelocity.getEagler().getSkinService().processForceSkin(clientUUID, epd);
}
if(forceCape != null && clientProtocolVersion >= 4) {
EaglerXVelocity.getEagler().getCapeService().processForceCape(clientUUID, epd);
}
EaglerPlayerData epd = ctx.channel().attr(EaglerPipeline.CONNECTION_INSTANCE).get().eaglerData = new EaglerPlayerData(conf, ctx.channel().attr(EaglerPipeline.ORIGIN).get(), cert);
EaglerXVelocity.logger().info("{} has connected", player);
if(conf.getEnableVoiceChat()) {
EaglerXVelocity.getEagler().getVoiceService().handlePlayerLoggedIn(epd);
}
if(clientProtocolVersion >= 4) {
SPacketCustomizePauseMenuV4EAG pauseMenuPkt = EaglerXVelocity.getEagler().getConfig().getPauseMenuConf().getPacket();
if(pauseMenuPkt != null) {
epd.sendEaglerMessage(pauseMenuPkt);
}
}
if(!blockUpdate) {
List<EaglerPlayerData.ClientCertificateHolder> set = EaglerUpdateSvc.getCertList();
synchronized(set) {
@ -1062,8 +1271,8 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if (reason.isPresent()) {
bungee.getEventManager().fireAndForget(new DisconnectEvent(player, DisconnectEvent.LoginStatus.CANCELLED_BY_PROXY));
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE,
JSONComponentSerializer.json().serialize(GlobalTranslator.render(reason.get(), Locale.getDefault())))
.addListener(ChannelFutureListener.CLOSE);
GlobalTranslator.render(reason.get(), Locale.getDefault()))
.addListener(ChannelFutureListener.CLOSE);
return;
} else {
if (!bungee.registerConnection(player)) {
@ -1274,13 +1483,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) {
@ -1298,13 +1507,13 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
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);
@ -1317,6 +1526,14 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
return ctx.writeAndFlush(new BinaryWebSocketFrame(buf));
}
private ChannelFuture sendErrorCode(ChannelHandlerContext ctx, int code, Component comp) {
if((!isProtocolExchanged || clientProtocolVersion == 2)) {
return sendErrorCode(ctx, code, LegacyComponentSerializer.legacySection().serialize(comp));
}else {
return sendErrorCode(ctx, code, JSONLegacySerializer.instance.serialize(comp));
}
}
public void channelInactive(ChannelHandlerContext ctx) {
connectionClosed = true;
EaglerPipeline.closeChannel(ctx.channel());

View File

@ -0,0 +1,307 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.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 com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
import io.netty.buffer.Unpooled;
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_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftVoiceStatusChangeEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayInputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayOutputStream;
/**
* 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(EaglerPlayerData eaglerHandler) {
return new BackendRPCSessionHandler(eaglerHandler);
}
protected final EaglerPlayerData eaglerHandler;
private ServerConnection 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(EaglerPlayerData eaglerHandler) {
this.eaglerHandler = eaglerHandler;
}
public void handleRPCPacket(ServerConnection 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) {
EaglerXVelocity.logger().error("[{}]: Recieved invalid backend RPC protocol packet for user \"{}\"",
eaglerHandler.getSocketAddress(), 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 {
EaglerXVelocity.logger().warn(
"[{}]: Failed to write backend RPC protocol version for user \"{}\", the RPC connection is not initialized!",
eaglerHandler.getSocketAddress(), eaglerHandler.getName());
}
}
protected void sendRPCPacket(EaglerBackendRPCProtocol protocol, ServerConnection 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) {
EaglerXVelocity.logger().warn(
"[{}]: Backend RPC packet type {} was the wrong length for user \"{}\" after serialization: {} != {}",
eaglerHandler.getSocketAddress(), packet.getClass().getSimpleName(), eaglerHandler.getName(),
ret.length, len);
}
sendPluginMessage(server, EaglerBackendRPCProtocol.CHANNEL_NAME, ret);
}
public void handleConnectionLost() {
if(currentServer != null) {
handleDestroyContext();
}
}
private void handleDestroyContext() {
currentServer = null;
currentProtocol = null;
currentHandler = null;
subscribedEvents = 0;
}
private void handleCreateContext(ServerConnection server, byte[] data) {
EaglerBackendRPCPacket packet;
try {
packet = decodeRPCPacket(EaglerBackendRPCProtocol.INIT, data);
} catch (IOException e) {
EaglerXVelocity.logger().error("[{}]: Recieved invalid backend RPC protocol handshake for user \"{}\"",
eaglerHandler.getSocketAddress(), eaglerHandler.getName(), e);
return;
}
if(!(packet instanceof CPacketRPCEnabled)) {
throw new WrongRPCPacketException();
}
if(!containsProtocol(((CPacketRPCEnabled)packet).supportedProtocols, EaglerBackendRPCProtocol.V1.vers)) {
EaglerXVelocity.logger().error("[{}]: Unsupported backend RPC protocol version for user \"{}\"", eaglerHandler.getSocketAddress(), 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);
}
private static boolean containsProtocol(int[] supportedProtocols, int vers) {
for(int i = 0; i < supportedProtocols.length; ++i) {
if(supportedProtocols[i] == vers) {
return true;
}
}
return false;
}
public static void handlePacketOnVanilla(ServerConnection server, ConnectedPlayer player, byte[] data) {
EaglerBackendRPCPacket packet;
try {
packet = EaglerBackendRPCProtocol.INIT.readPacket(new DataInputStream(new ByteArrayInputStream(data)), EaglerBackendRPCProtocol.CLIENT_TO_SERVER);
} catch (IOException e) {
EaglerXVelocity.logger().error("[{}]: Recieved invalid backend RPC protocol handshake for user \"{}\"", player.getRemoteAddress(), player.getUsername(), e);
EaglerXVelocity.logger().error("(Note: this player is not using Eaglercraft!)");
return;
}
if(!(packet instanceof CPacketRPCEnabled)) {
throw new WrongRPCPacketException();
}
if(!containsProtocol(((CPacketRPCEnabled)packet).supportedProtocols, EaglerBackendRPCProtocol.V1.vers)) {
EaglerXVelocity.logger().error("[{}]: Unsupported backend RPC protocol version for user \"{}\"", player.getRemoteAddress(), player.getUsername());
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) {
EaglerXVelocity.logger().error("[{}]: Failed to write backend RPC protocol version for user \"{}\"", player.getRemoteAddress(), player.getUsername(), ex);
EaglerXVelocity.logger().error("(Note: this player is not using Eaglercraft!)");
return;
}
sendPluginMessage(server, EaglerBackendRPCProtocol.CHANNEL_NAME, bao.toByteArray());
return;
}
EaglerXVelocity.logger().warn("[{}]: Tried to open backend RPC protocol connection for non-eagler player \"{}\"", player.getRemoteAddress(), player.getUsername());
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) {
EaglerXVelocity.logger().error("[{}]: Failed to write backend RPC protocol version for user \"{}\"", player.getRemoteAddress(), player.getUsername(), ex);
return;
}
sendPluginMessage(server, 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 = EaglerXVelocity.getEagler().getVoiceService();
if(svc != null && eaglerHandler.getEaglerListenerConfig().getEnableVoiceChat()) {
EnumVoiceState state = svc.getPlayerVoiceState(eaglerHandler.getUniqueId(), currentServer.getServerInfo());
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) {
EaglerXVelocity.logger().error(
"[{}]: Unsupported events were subscribed to for \"{}\" via backend RPC protocol, bitfield: {}",
eaglerHandler.getSocketAddress(), eaglerHandler.getName(), 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));
}
}
}
public static void sendPluginMessage(ServerConnection conn, String channel, byte[] data) {
// Velocity channel registry can go fuck itself
MinecraftConnection mc = ((VelocityServerConnection)conn).getConnection();
if(mc != null) {
mc.write(new PluginMessagePacket(channel, Unpooled.wrappedBuffer(data)));
}
}
}

View File

@ -0,0 +1,22 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.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,439 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.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 com.velocitypowered.api.proxy.ServerConnection;
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_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EnumWebViewState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerPauseMenuConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.protocol.GameProtocolMessageController;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.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;
/**
* 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 ServerConnection server;
protected final EaglerPlayerData eaglerHandler;
public ServerV1RPCProtocolHandler(BackendRPCSessionHandler sessionHandler, ServerConnection server, EaglerPlayerData 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 = EaglerXVelocity.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 = EaglerXVelocity.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 = EaglerXVelocity.getEagler().getVoiceService();
if(svc != null && eaglerHandler.getEaglerListenerConfig().getEnableVoiceChat()) {
EnumVoiceState enumVoiceState = svc.getPlayerVoiceState(eaglerHandler.getUniqueId(), server.getServerInfo());
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();
}
EaglerXVelocityAPIHelper.changePlayerSkinPreset(EaglerXVelocityAPIHelper.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);
EaglerXVelocityAPIHelper.changePlayerSkinCustom(EaglerXVelocityAPIHelper.getPlayer(eaglerHandler),
bs[1] & 0xFF, cust, packet.notifyOthers);
}else {
throw new IOException();
}
}catch(IOException ex) {
EaglerXVelocity.logger().error(
"[{}]: Invalid CPacketRPCSetPlayerSkin packet recieved for player \"{}\" from backend RPC protocol!",
eaglerHandler.getSocketAddress(), eaglerHandler.getName());
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();
}
EaglerXVelocityAPIHelper.changePlayerCapePreset(EaglerXVelocityAPIHelper.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);
EaglerXVelocityAPIHelper.changePlayerCapeCustom(EaglerXVelocityAPIHelper.getPlayer(eaglerHandler),
cust, true, packet.notifyOthers);
}else {
throw new IOException();
}
}catch(IOException ex) {
EaglerXVelocity.logger().error(
"[{}]: Invalid CPacketRPCSetPlayerCape packet recieved for player \"{}\" from backend RPC protocol!",
eaglerHandler.getSocketAddress(), eaglerHandler.getName());
return;
}
}
public void handleClient(CPacketRPCSetPlayerCookie packet) {
eaglerHandler.setCookieData(packet.cookieData, packet.expires, packet.revokeQuerySupported, packet.saveToDisk);
}
public void handleClient(CPacketRPCSetPlayerFNAWEn packet) {
EaglerXVelocityAPIHelper.setEnableForceFNAWSkins(eaglerHandler, packet.enable, packet.force);
}
public void handleClient(CPacketRPCRedirectPlayer packet) {
eaglerHandler.redirectPlayerToWebSocket(packet.redirectURI);
}
public void handleClient(CPacketRPCResetPlayerMulti packet) {
EaglerXVelocityAPIHelper.resetPlayerMulti(EaglerXVelocityAPIHelper.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 = EaglerXVelocity.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 {
EaglerXVelocity.logger().warn(
"[{}]: Recieved packet CPacketRPCSetPauseMenuCustom for player \"{}\" from backend RPC protocol, but their client does not support pause menu customization!",
eaglerHandler.getSocketAddress(), eaglerHandler.getName());
}
}
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 {
EaglerXVelocity.logger().warn(
"[{}]: Recieved packet CPacketRPCNotifIconRegister for player \"{}\" from backend RPC protocol, but their client does not support notifications!",
eaglerHandler.getSocketAddress(), eaglerHandler.getName());
}
}
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 {
EaglerXVelocity.logger().warn(
"[{}]: Recieved packet CPacketRPCNotifIconRelease for player \"{}\" from backend RPC protocol, but their client does not support notifications!",
eaglerHandler.getSocketAddress(), eaglerHandler.getName());
}
}
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 {
EaglerXVelocity.logger().warn(
"[{}]: Recieved packet CPacketRPCNotifBadgeShow for player \"{}\" from backend RPC protocol, but their client does not support notifications!",
eaglerHandler.getSocketAddress(), eaglerHandler.getName());
}
}
public void handleClient(CPacketRPCNotifBadgeHide packet) {
if(eaglerHandler.notificationSupported()) {
eaglerHandler.sendEaglerMessage(new SPacketNotifBadgeHideV4EAG(packet.badgeUUID.getMostSignificantBits(),
packet.badgeUUID.getLeastSignificantBits()));
}else {
EaglerXVelocity.logger().warn(
"[{}]: Recieved packet CPacketRPCNotifBadgeHide for player \"{}\" from backend RPC protocol, but their client does not support notifications!",
eaglerHandler.getSocketAddress(), eaglerHandler.getName());
}
}
public void handleClient(CPacketRPCDisabled packet) {
sessionHandler.handleDisabled();
}
public void handleClient(CPacketRPCSendRawMessage packet) {
GameProtocolMessageController.sendPluginMessage(eaglerHandler.getEaglerMessageController().getUserConnection(), packet.messageChannel, packet.messageData);
}
}

View File

@ -0,0 +1,302 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.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 com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.concurrent.ScheduledFuture;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
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;
/**
* 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 ConnectedPlayer 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(ConnectedPlayer 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) {
EaglerXVelocity.logger().warn("Packet {} was the wrong length after serialization, {} != {}",
packet.getClass().getSimpleName(), 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) {
sendPluginMessage(owner, chan, data);
}else {
sendQueueV4.add(data);
if(futureSendTaskV4 == null || futureSendTaskV4.isDone()) {
futureSendTaskV4 = owner.getConnection().getChannel()
.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 {
sendPluginMessage(owner, chan, data);
}
}
private void flushQueue() {
if(!owner.isActive()) {
sendQueueV4.clear();
return;
}
byte[] pkt;
if(sendQueueV4.size() == 1) {
pkt = sendQueueV4.remove(0);
sendPluginMessage(owner, 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);
sendPluginMessage(owner, 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);
ProtocolUtils.writeVarInt(sendBuffer, sendCount);
for(j = 0; j < sendCount; ++j) {
pkt = sendQueueV4.remove(0);
ProtocolUtils.writeVarInt(sendBuffer, pkt.length);
sendBuffer.writeBytes(pkt);
}
sendPluginMessage(owner, GamePluginMessageConstants.V4_CHANNEL, toSend);
}
}
}
public static GameMessageHandler createServerHandler(int protocolVersion, ConnectedPlayer conn, EaglerPlayerData playerData, EaglerXVelocity plugin) {
switch(protocolVersion) {
case 2:
case 3:
return new ServerV3MessageHandler(playerData, plugin);
case 4:
return new ServerV4MessageHandler(conn, playerData, plugin);
default:
throw new IllegalArgumentException("Unknown protocol verison: " + protocolVersion);
}
}
public static void sendHelper(ConnectedPlayer conn, GameMessagePacket packet) {
EaglerPlayerData p = EaglerPipeline.getEaglerHandle(conn);
if(p != null) {
p.sendEaglerMessage(packet);
}else {
throw new UnsupportedOperationException("Tried to send eagler packet on a non-eagler connection!");
}
}
public static void sendPluginMessage(ConnectedPlayer conn, String channel, byte[] data) {
// Velocity channel registry can go fuck itself
MinecraftConnection mc = conn.getConnection();
if(mc != null) {
mc.write(new PluginMessagePacket(channel, Unpooled.wrappedBuffer(data)));
}
}
public ConnectedPlayer getUserConnection() {
return owner;
}
}

View File

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

View File

@ -0,0 +1,180 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.protocol;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import net.kyori.adventure.text.Component;
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_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftWebViewChannelEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftWebViewMessageEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerPauseMenuConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.backend_rpc_protocol.EnumSubscribedEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.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.*;
/**
* 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 ConnectedPlayer conn;
private final EaglerPlayerData eaglerHandle;
private final EaglerXVelocity plugin;
public ServerV4MessageHandler(ConnectedPlayer conn, EaglerPlayerData eaglerHandle, EaglerXVelocity plugin) {
this.conn = conn;
this.eaglerHandle = eaglerHandle;
this.plugin = plugin;
}
public void handleClient(CPacketGetOtherCapeEAG packet) {
plugin.getCapeService().processGetOtherCape(new UUID(packet.uuidMost, packet.uuidLeast), eaglerHandle);
}
public void handleClient(CPacketGetOtherSkinEAG packet) {
plugin.getSkinService().processGetOtherSkin(new UUID(packet.uuidMost, packet.uuidLeast), eaglerHandle);
}
public void handleClient(CPacketGetSkinByURLEAG packet) {
plugin.getSkinService().processGetOtherSkin(new UUID(packet.uuidMost, packet.uuidLeast), packet.url, eaglerHandle);
}
public void handleClient(CPacketVoiceSignalConnectEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && eaglerHandle.getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeConnect(eaglerHandle);
}
}
public void handleClient(CPacketVoiceSignalDescEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && eaglerHandle.getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeDesc(new UUID(packet.uuidMost, packet.uuidLeast), packet.desc, eaglerHandle);
}
}
public void handleClient(CPacketVoiceSignalDisconnectV4EAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && eaglerHandle.getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeDisconnect(eaglerHandle);
}
}
public void handleClient(CPacketVoiceSignalDisconnectPeerV4EAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && eaglerHandle.getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeDisconnectPeer(new UUID(packet.uuidMost, packet.uuidLeast), eaglerHandle);
}
}
public void handleClient(CPacketVoiceSignalICEEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && eaglerHandle.getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeICE(new UUID(packet.uuidMost, packet.uuidLeast), packet.ice, eaglerHandle);
}
}
public void handleClient(CPacketVoiceSignalRequestEAG packet) {
VoiceService svc = plugin.getVoiceService();
if(svc != null && eaglerHandle.getEaglerListenerConfig().getEnableVoiceChat()) {
svc.handleVoiceSignalPacketTypeRequest(new UUID(packet.uuidMost, packet.uuidLeast), eaglerHandle);
}
}
public void handleClient(CPacketGetOtherClientUUIDV4EAG packet) {
Optional<Player> player = plugin.getProxy().getPlayer(new UUID(packet.playerUUIDMost, packet.playerUUIDLeast));
if(player.isPresent()) {
EaglerPlayerData conn2 = EaglerPipeline.getEaglerHandle(player.get());
if(conn2 != null) {
UUID uuid = conn2.getClientBrandUUID();
if (uuid != null) {
eaglerHandle.sendEaglerMessage(new SPacketOtherPlayerClientUUIDV4EAG(packet.requestId,
uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()));
return;
}
}else {
eaglerHandle.sendEaglerMessage(new SPacketOtherPlayerClientUUIDV4EAG(packet.requestId,
EaglerXVelocityAPIHelper.BRAND_VANILLA_UUID.getMostSignificantBits(),
EaglerXVelocityAPIHelper.BRAND_VANILLA_UUID.getLeastSignificantBits()));
return;
}
}
eaglerHandle.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)) {
synchronized(eaglerHandle.serverInfoSendBuffer) {
if(eaglerHandle.hasSentServerInfo.getAndSet(true)) {
eaglerHandle.getPlayerObj().disconnect(Component.text("Duplicate server info request"));
return;
}
eaglerHandle.serverInfoSendBuffer.clear();
eaglerHandle.serverInfoSendBuffer.addAll(conf.getServerInfo());
}
}else {
eaglerHandle.getPlayerObj().disconnect(Component.text("Invalid server info request"));
}
}
public void handleClient(CPacketWebViewMessageV4EAG packet) {
if(eaglerHandle.isWebViewChannelAllowed) {
if(eaglerHandle.webViewMessageChannelOpen.get()) {
if(eaglerHandle.getRPCEventSubscribed(EnumSubscribedEvent.WEBVIEW_MESSAGE)) {
eaglerHandle.getRPCSessionHandler().sendRPCPacket(new SPacketRPCEventWebViewMessage(
eaglerHandle.webViewMessageChannelName, packet.type, packet.data));
}
plugin.getProxy().getEventManager().fire(new EaglercraftWebViewMessageEvent(conn,
eaglerHandle.getEaglerListenerConfig(), eaglerHandle.webViewMessageChannelName, packet));
}
}else {
eaglerHandle.getPlayerObj().disconnect(Component.text("Webview channel permissions have not been enabled!"));
}
}
public void handleClient(CPacketWebViewMessageEnV4EAG packet) {
if(eaglerHandle.isWebViewChannelAllowed) {
eaglerHandle.webViewMessageChannelOpen.set(packet.messageChannelOpen);
String oldChannelName = eaglerHandle.webViewMessageChannelName;
eaglerHandle.webViewMessageChannelName = packet.messageChannelOpen ? packet.channelName : null;
if(eaglerHandle.getRPCEventSubscribed(EnumSubscribedEvent.WEBVIEW_OPEN_CLOSE)) {
eaglerHandle.getRPCSessionHandler().sendRPCPacket(new SPacketRPCEventWebViewOpenClose(
packet.messageChannelOpen, packet.messageChannelOpen ? packet.channelName : oldChannelName));
}
plugin.getProxy().getEventManager()
.fire(new EaglercraftWebViewChannelEvent(conn, eaglerHandle.getEaglerListenerConfig(),
packet.messageChannelOpen ? eaglerHandle.webViewMessageChannelName : oldChannelName,
packet.messageChannelOpen ? EaglercraftWebViewChannelEvent.EventType.CHANNEL_OPEN
: EaglercraftWebViewChannelEvent.EventType.CHANNEL_CLOSE));
}else {
eaglerHandle.getPlayerObj().disconnect(Component.text("Webview channel permissions have not been enabled!"));
}
}
}

View File

@ -9,6 +9,7 @@ import com.google.gson.JsonObject;
import com.velocitypowered.api.proxy.Player;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.query.EaglerQuerySimpleHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.query.MOTDConnection;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerListenerConfig;
@ -46,7 +47,7 @@ public class MOTDQueryHandler extends EaglerQuerySimpleHandler implements MOTDCo
@Override
protected void begin(String queryType) {
creationTime = System.currentTimeMillis();
creationTime = EaglerXVelocityAPIHelper.steadyTimeMillis();
subType = queryType;
returnType = "MOTD";
EaglerListenerConfig listener = getListener();
@ -59,7 +60,7 @@ public class MOTDQueryHandler extends EaglerQuerySimpleHandler implements MOTDCo
}
maxPlayers = listener.getMaxPlayer();
onlinePlayers = EaglerXVelocity.proxy().getPlayerCount();
players = new ArrayList();
players = new ArrayList<>();
for(Player pp : EaglerXVelocity.proxy().getAllPlayers()) {
players.add(pp.getUsername());
if(players.size() >= 9) {

View File

@ -7,6 +7,7 @@ import com.google.gson.JsonObject;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocityVersion;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerVelocityConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.HttpServerQueryHandler;
@ -27,12 +28,13 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.HttpServerQu
*/
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) {
@ -77,7 +79,7 @@ public class QueryManager {
json.addProperty("brand", "lax1dude");
json.addProperty("vers", EaglerXVelocityVersion.ID + "/" + EaglerXVelocityVersion.VERSION);
json.addProperty("cracked", conf.isCracked());
json.addProperty("time", System.currentTimeMillis());
json.addProperty("time", EaglerXVelocityAPIHelper.steadyTimeMillis());
json.addProperty("uuid", conf.getServerUUID().toString());
return json;
}

View File

@ -0,0 +1,74 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.query;
import com.google.gson.JsonObject;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftRevokeSessionQueryEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftRevokeSessionQueryEvent.EnumSessionRevokeStatus;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.query.EaglerQueryHandler;
/**
* 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);
evt = EaglerXVelocity.proxy().getEventManager().fire(evt).join();
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,17 +4,18 @@ 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_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocityVersion;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
@ -45,13 +46,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 = EaglerXVelocityAPIHelper.steadyTimeMillis();
synchronized(cacheClearLock) {
synchronized(filesCache) {
Iterator<HttpMemoryCache> itr = filesCache.values().iterator();
@ -70,7 +71,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<>();
for(int i = 0; i < pathSplit.length; ++i) {
pathSplit[i] = pathSplit[i].trim();
if(pathSplit[i].length() > 0) {
@ -198,7 +199,7 @@ public class HttpWebServer {
if(ct == null) {
ct = HttpContentType.defaultType;
}
long millis = System.currentTimeMillis();
long millis = EaglerXVelocityAPIHelper.steadyTimeMillis();
return new HttpMemoryCache(path, requestCachePath, file, ct, millis, millis, path.lastModified());
}catch(Throwable t) {
return null;
@ -209,7 +210,7 @@ public class HttpWebServer {
if(file.fileObject == null) {
return file;
}
long millis = System.currentTimeMillis();
long millis = EaglerXVelocityAPIHelper.steadyTimeMillis();
file.lastCacheHit = millis;
if(millis - file.lastDiskReload > 4000l) {
File f = file.fileObject;
@ -265,8 +266,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(EaglerXVelocityVersion.NAME) + "/"
+ htmlEntities(EaglerXVelocityVersion.VERSION) + "</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 = EaglerXVelocityAPIHelper.steadyTimeMillis();
return new HttpMemoryCache(null, "~404", Unpooled.wrappedBuffer(src), htmlContentType, millis, millis, millis);
}
@ -281,8 +282,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 = EaglerXVelocityAPIHelper.steadyTimeMillis();
return new HttpMemoryCache(null, "~404", Unpooled.wrappedBuffer(src), htmlContentType, millis, millis, millis);
}

View File

@ -105,7 +105,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) {
@ -113,7 +113,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

@ -3,7 +3,9 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins;
import java.io.IOException;
import java.util.UUID;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
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, ConnectedPlayer 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, ConnectedPlayer 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

@ -4,11 +4,12 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
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;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
@ -29,26 +30,41 @@ public class CapeServiceOffline {
public static final int masterRateLimitPerPlayer = 250;
public static final ChannelIdentifier CHANNEL = new LegacyChannelIdentifier("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, ConnectedPlayer sender) {
if(EaglerPipeline.getEaglerHandle(sender).skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
byte[] maybeCape;
public void processGetOtherCape(UUID searchUUID, EaglerPlayerData sender) {
if(sender.skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
GameMessagePacket maybeCape;
synchronized(capesCache) {
maybeCape = capesCache.get(searchUUID);
}
if(maybeCape != null) {
sender.sendPluginMessage(CapeServiceOffline.CHANNEL, maybeCape);
sender.sendEaglerMessage(maybeCape);
}else {
sender.sendPluginMessage(CapeServiceOffline.CHANNEL, CapePackets.makePresetResponse(searchUUID, 0));
sender.sendEaglerMessage(new SPacketOtherCapePresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), 0));
}
}
}
public void processForceCape(UUID clientUUID, EaglerPlayerData 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));
}
}
}
@ -59,6 +75,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

@ -2,10 +2,11 @@ package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins;
import java.util.UUID;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
/**
* 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
@ -24,11 +25,11 @@ public interface ISkinService {
void init(String uri, String driverClass, String driverPath, int keepObjectsDays, int keepProfilesDays,
int maxObjects, int maxProfiles);
void processGetOtherSkin(final UUID searchUUID, final ConnectedPlayer sender);
void processGetOtherSkin(final UUID searchUUID, final EaglerPlayerData sender);
void processGetOtherSkin(UUID searchUUID, String skinURL, ConnectedPlayer sender);
void processGetOtherSkin(UUID searchUUID, String skinURL, EaglerPlayerData sender);
void registerEaglercraftPlayer(UUID clientUUID, byte[] generatedPacket, int modelId);
void registerEaglercraftPlayer(UUID clientUUID, SkinPacketVersionCache generatedPacket, int modelId);
void unregisterPlayer(UUID clientUUID);
@ -42,4 +43,8 @@ public interface ISkinService {
void shutdown();
void processForceSkin(UUID playerUUID, EaglerPlayerData initialHandler);
SkinPacketVersionCache getSkin(UUID playerUUID);
}

View File

@ -15,6 +15,7 @@ import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.sqlite.EaglerDrivers;
/**
@ -304,9 +305,10 @@ public class JDBCCacheProvider implements ICacheProvider {
@Override
public void flush() {
long millis = System.currentTimeMillis();
if(millis - lastFlush > 1200000l) { // 30 minutes
lastFlush = millis;
long steadyMillis = EaglerXVelocityAPIHelper.steadyTimeMillis();
if(steadyMillis - lastFlush > 1200000l) { // 30 minutes
lastFlush = steadyMillis;
long millis = System.currentTimeMillis();
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_velocity.skins;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EaglerXVelocityAPIHelper;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
*
@ -21,13 +23,13 @@ public class SimpleRateLimiter {
private int count;
public SimpleRateLimiter() {
timer = System.currentTimeMillis();
timer = EaglerXVelocityAPIHelper.steadyTimeMillis();
count = 0;
}
public boolean rateLimit(int maxPerMinute) {
int t = 60000 / maxPerMinute;
long millis = System.currentTimeMillis();
long millis = EaglerXVelocityAPIHelper.steadyTimeMillis();
int decr = (int)(millis - timer) / t;
if(decr > 0) {
timer += decr * t;

View File

@ -1,13 +1,13 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.skins;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
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.
@ -28,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, ConnectedPlayer 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, ConnectedPlayer 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, ConnectedPlayer 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(EaglerXVelocity.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) {
@ -108,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) {
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) {
@ -203,27 +110,6 @@ public class SkinPackets {
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) {
@ -240,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

@ -8,9 +8,10 @@ import java.util.UUID;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketOtherSkinPresetEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
@ -34,16 +35,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();
@ -54,21 +55,20 @@ public class SkinServiceOffline implements ISkinService {
}
}
public void processGetOtherSkin(UUID searchUUID, ConnectedPlayer sender) {
if(EaglerPipeline.getEaglerHandle(sender).skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
CachedSkin cached;
synchronized(skinCache) {
cached = skinCache.get(searchUUID);
}
if(cached != null) {
sender.sendPluginMessage(SkinService.CHANNEL, cached.packet);
}else {
sender.sendPluginMessage(SkinService.CHANNEL, SkinPackets.makePresetResponse(searchUUID));
}
public void processGetOtherSkin(UUID searchUUID, EaglerPlayerData sender) {
CachedSkin cached;
synchronized(skinCache) {
cached = skinCache.get(searchUUID);
}
if(cached != null) {
sender.sendEaglerMessage(cached.packet.get(sender.getEaglerProtocol()));
}else {
sender.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), (searchUUID.hashCode() & 1) != 0 ? 1 : 0));
}
}
public void processGetOtherSkin(UUID searchUUID, String skinURL, ConnectedPlayer sender) {
public void processGetOtherSkin(UUID searchUUID, String skinURL, EaglerPlayerData sender) {
Collection<UUID> uuids;
synchronized(onlinePlayersFromTexturesMap) {
uuids = onlinePlayersFromTexturesMap.get(searchUUID);
@ -80,15 +80,23 @@ public class SkinServiceOffline implements ISkinService {
while(uuidItr.hasNext()) {
cached = skinCache.get(uuidItr.next());
if(cached != null) {
sender.sendPluginMessage(SkinService.CHANNEL, SkinPackets.rewriteUUID(searchUUID, cached.packet));
sender.sendEaglerMessage(cached.packet.get(sender.getEaglerProtocol(),
searchUUID.getMostSignificantBits(), searchUUID.getLeastSignificantBits()));
return;
}
}
}
}
sender.sendPluginMessage(SkinService.CHANNEL, SkinPackets.makePresetResponse(searchUUID));
if(skinURL.startsWith("eagler://")) { // customs skulls from exported singleplayer worlds
sender.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), 0));
return;
}
sender.sendEaglerMessage(new SPacketOtherSkinPresetEAG(searchUUID.getMostSignificantBits(),
searchUUID.getLeastSignificantBits(), (searchUUID.hashCode() & 1) != 0 ? 1 : 0));
}
public void registerEaglercraftPlayer(UUID clientUUID, byte[] generatedPacket, int modelId) {
public void registerEaglercraftPlayer(UUID clientUUID, SkinPacketVersionCache generatedPacket, int modelId) {
synchronized(skinCache) {
skinCache.put(clientUUID, new CachedSkin(clientUUID, generatedPacket));
}
@ -106,6 +114,16 @@ public class SkinServiceOffline implements ISkinService {
}
}
public void processForceSkin(UUID playerUUID, EaglerPlayerData initialHandler) {
CachedSkin cached;
synchronized(skinCache) {
cached = skinCache.get(playerUUID);
}
if(cached != null) {
initialHandler.sendEaglerMessage(cached.packet.getForceClientV4());
}
}
public void flush() {
// no
}
@ -116,4 +134,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

@ -45,7 +45,7 @@ public class EaglerDrivers {
if(!driver.exists()) {
try {
URL u = new URL("https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.45.0.0/sqlite-jdbc-3.45.0.0.jar");
EaglerXVelocity.logger().info("Downloading from maven: " + u.toString());
EaglerXVelocity.logger().info("Downloading from maven: {}", u);
copyURLToFile(u, driver);
} catch (Throwable ex) {
EaglerXVelocity.logger().error("Could not download sqlite-jdbc.jar from repo1.maven.org!");
@ -67,7 +67,7 @@ public class EaglerDrivers {
driversJARs.put(address, classLoader);
}
Class loadedDriver;
Class<?> loadedDriver;
try {
loadedDriver = classLoader.loadClass(driverClass);
}catch(ClassNotFoundException ex) {
@ -93,8 +93,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_velocity.api.EaglerXVelocityAPIHelper;
/**
* 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 = EaglerXVelocityAPIHelper.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, EaglerXVelocityAPIHelper.steadyTimeMillis());
return success;
}

View File

@ -1,5 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.voice;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -8,9 +10,19 @@ import java.util.Set;
import java.util.UUID;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventToggledVoice;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftVoiceStatusChangeEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.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.SPacketVoiceSignalConnectV3EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketVoiceSignalConnectV4EAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketVoiceSignalDescEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketVoiceSignalDisconnectPeerEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketVoiceSignalGlobalEAG;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketVoiceSignalICEEAG;
/**
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
@ -30,9 +42,9 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPipeli
public class VoiceServerImpl {
private final ServerInfo server;
private final byte[] iceServersPacket;
private final GameMessagePacket iceServersPacket;
private final Map<UUID, ConnectedPlayer> voicePlayers = new HashMap<>();
private final Map<UUID, EaglerPlayerData> voicePlayers = new HashMap<>();
private final Map<UUID, ExpiringSet<UUID>> voiceRequests = new HashMap<>();
private final Set<VoicePair> voicePairs = new HashSet<>();
@ -71,27 +83,31 @@ public class VoiceServerImpl {
}
}
VoiceServerImpl(ServerInfo server, byte[] iceServersPacket) {
VoiceServerImpl(ServerInfo server, GameMessagePacket iceServersPacket) {
this.server = server;
this.iceServersPacket = iceServersPacket;
}
public void handlePlayerLoggedIn(ConnectedPlayer player) {
player.sendPluginMessage(VoiceService.CHANNEL, iceServersPacket);
public void handlePlayerLoggedIn(EaglerPlayerData player) {
player.sendEaglerMessage(iceServersPacket);
player.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.DISABLED);
if(player.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
player.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_DISABLED);
}
}
public void handlePlayerLoggedOut(ConnectedPlayer player) {
public void handlePlayerLoggedOut(EaglerPlayerData player) {
removeUser(player.getUniqueId());
}
void handleVoiceSignalPacketTypeRequest(UUID player, ConnectedPlayer sender) {
void handleVoiceSignalPacketTypeRequest(UUID player, EaglerPlayerData sender) {
synchronized (voicePlayers) {
UUID senderUUID = sender.getUniqueId();
if (senderUUID.equals(player))
return; // prevent duplicates
if (!voicePlayers.containsKey(senderUUID))
return;
ConnectedPlayer targetPlayerCon = voicePlayers.get(player);
EaglerPlayerData targetPlayerCon = voicePlayers.get(player);
if (targetPlayerCon == null)
return;
VoicePair newPair = new VoicePair(player, senderUUID);
@ -116,18 +132,32 @@ public class VoiceServerImpl {
voiceRequests.remove(senderUUID);
// send each other add data
voicePairs.add(newPair);
targetPlayerCon.sendPluginMessage(VoiceService.CHANNEL,
VoiceSignalPackets.makeVoiceSignalPacketConnect(senderUUID, false));
sender.sendPluginMessage(VoiceService.CHANNEL,
VoiceSignalPackets.makeVoiceSignalPacketConnect(player, true));
if (targetPlayerCon.getEaglerProtocol().ver <= 3) {
targetPlayerCon.sendEaglerMessage(new SPacketVoiceSignalConnectV3EAG(
senderUUID.getMostSignificantBits(), senderUUID.getLeastSignificantBits(), false, false));
} else {
targetPlayerCon.sendEaglerMessage(new SPacketVoiceSignalConnectV4EAG(
senderUUID.getMostSignificantBits(), senderUUID.getLeastSignificantBits(), false));
}
if (sender.getEaglerProtocol().ver <= 3) {
sender.sendEaglerMessage(new SPacketVoiceSignalConnectV3EAG(
player.getMostSignificantBits(), player.getLeastSignificantBits(), false, true));
} else {
sender.sendEaglerMessage(new SPacketVoiceSignalConnectV4EAG(
player.getMostSignificantBits(), player.getLeastSignificantBits(), true));
}
}
}
}
void handleVoiceSignalPacketTypeConnect(ConnectedPlayer sender) {
if(!EaglerPipeline.getEaglerHandle(sender).voiceConnectRateLimiter.rateLimit(VOICE_CONNECT_RATELIMIT)) {
void handleVoiceSignalPacketTypeConnect(EaglerPlayerData sender) {
if(!sender.voiceConnectRateLimiter.rateLimit(VOICE_CONNECT_RATELIMIT)) {
return;
}
sender.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.ENABLED);
if(sender.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
sender.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_ENABLED);
}
synchronized (voicePlayers) {
if (voicePlayers.containsKey(sender.getUniqueId())) {
return;
@ -137,87 +167,103 @@ public class VoiceServerImpl {
if (hasNoOtherPlayers) {
return;
}
byte[] packetToBroadcast = VoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values());
for (ConnectedPlayer userCon : voicePlayers.values()) {
userCon.sendPluginMessage(VoiceService.CHANNEL, packetToBroadcast);
Collection<SPacketVoiceSignalGlobalEAG.UserData> userDatas = new ArrayList<>(voicePlayers.size());
for(EaglerPlayerData userCon : voicePlayers.values()) {
UUID uuid = userCon.getUniqueId();
userDatas.add(new SPacketVoiceSignalGlobalEAG.UserData(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits(), userCon.getName()));
}
GameMessagePacket packetToBroadcast = new SPacketVoiceSignalGlobalEAG(userDatas);
for (EaglerPlayerData userCon : voicePlayers.values()) {
userCon.sendEaglerMessage(packetToBroadcast);
}
}
}
void handleVoiceSignalPacketTypeICE(UUID player, String str, ConnectedPlayer sender) {
ConnectedPlayer pass;
void handleVoiceSignalPacketTypeICE(UUID player, byte[] str, EaglerPlayerData sender) {
EaglerPlayerData pass;
VoicePair pair = new VoicePair(player, sender.getUniqueId());
synchronized (voicePlayers) {
pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null;
}
if (pass != null) {
pass.sendPluginMessage(VoiceService.CHANNEL,
VoiceSignalPackets.makeVoiceSignalPacketICE(sender.getUniqueId(), str));
UUID uuid = sender.getUniqueId();
pass.sendEaglerMessage(
new SPacketVoiceSignalICEEAG(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits(), str));
}
}
void handleVoiceSignalPacketTypeDesc(UUID player, String str, ConnectedPlayer sender) {
ConnectedPlayer pass;
void handleVoiceSignalPacketTypeDesc(UUID player, byte[] str, EaglerPlayerData sender) {
EaglerPlayerData pass;
VoicePair pair = new VoicePair(player, sender.getUniqueId());
synchronized (voicePlayers) {
pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null;
}
if (pass != null) {
pass.sendPluginMessage(VoiceService.CHANNEL,
VoiceSignalPackets.makeVoiceSignalPacketDesc(sender.getUniqueId(), str));
UUID uuid = sender.getUniqueId();
pass.sendEaglerMessage(
new SPacketVoiceSignalDescEAG(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits(), str));
}
}
void handleVoiceSignalPacketTypeDisconnect(UUID player, ConnectedPlayer sender) {
if (player != null) {
synchronized (voicePlayers) {
if (!voicePlayers.containsKey(player)) {
return;
void handleVoiceSignalPacketTypeDisconnect(EaglerPlayerData sender) {
removeUser(sender.getUniqueId());
}
void handleVoiceSignalPacketTypeDisconnectPeer(UUID player, EaglerPlayerData sender) {
synchronized (voicePlayers) {
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;
}
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();
ConnectedPlayer conn = voicePlayers.get(target);
if (conn != null) {
if (userDisconnectPacket == null) {
userDisconnectPacket = VoiceSignalPackets.makeVoiceSignalPacketDisconnect(player);
}
conn.sendPluginMessage(VoiceService.CHANNEL, userDisconnectPacket);
}
sender.sendPluginMessage(VoiceService.CHANNEL,
VoiceSignalPackets.makeVoiceSignalPacketDisconnect(target));
if (target != null) {
pairsItr.remove();
EaglerPlayerData conn = voicePlayers.get(target);
if (conn != null) {
conn.sendEaglerMessage(new SPacketVoiceSignalDisconnectPeerEAG(player.getMostSignificantBits(),
player.getLeastSignificantBits()));
}
sender.sendEaglerMessage(new SPacketVoiceSignalDisconnectPeerEAG(target.getMostSignificantBits(),
target.getLeastSignificantBits()));
}
}
} else {
removeUser(sender.getUniqueId());
}
}
public void removeUser(UUID user) {
synchronized (voicePlayers) {
if (voicePlayers.remove(user) == null) {
EaglerPlayerData connRemove;
if ((connRemove = voicePlayers.remove(user)) == null) {
return;
}else {
connRemove.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.DISABLED);
if(connRemove.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
connRemove.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_DISABLED);
}
}
voiceRequests.remove(user);
if (voicePlayers.size() > 0) {
byte[] voicePlayersPkt = VoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values());
for (ConnectedPlayer userCon : voicePlayers.values()) {
Collection<SPacketVoiceSignalGlobalEAG.UserData> userDatas = new ArrayList<>(voicePlayers.size());
for(EaglerPlayerData userCon : voicePlayers.values()) {
UUID uuid = userCon.getUniqueId();
userDatas.add(new SPacketVoiceSignalGlobalEAG.UserData(uuid.getMostSignificantBits(),
uuid.getLeastSignificantBits(), userCon.getName()));
}
GameMessagePacket voicePlayersPkt = new SPacketVoiceSignalGlobalEAG(userDatas);
for (EaglerPlayerData userCon : voicePlayers.values()) {
if (!user.equals(userCon.getUniqueId())) {
userCon.sendPluginMessage(VoiceService.CHANNEL, voicePlayersPkt);
userCon.sendEaglerMessage(voicePlayersPkt);
}
}
}
byte[] userDisconnectPacket = null;
Iterator<VoicePair> pairsItr = voicePairs.iterator();
while (pairsItr.hasNext()) {
VoicePair voicePair = pairsItr.next();
@ -230,12 +276,10 @@ public class VoiceServerImpl {
if (target != null) {
pairsItr.remove();
if (voicePlayers.size() > 0) {
ConnectedPlayer conn = voicePlayers.get(target);
EaglerPlayerData conn = voicePlayers.get(target);
if (conn != null) {
if (userDisconnectPacket == null) {
userDisconnectPacket = VoiceSignalPackets.makeVoiceSignalPacketDisconnect(user);
}
conn.sendPluginMessage(VoiceService.CHANNEL, userDisconnectPacket);
conn.sendEaglerMessage(new SPacketVoiceSignalDisconnectPeerEAG(
user.getMostSignificantBits(), user.getLeastSignificantBits()));
}
}
}
@ -243,4 +287,13 @@ public class VoiceServerImpl {
}
}
EnumVoiceState getPlayerVoiceState(UUID uniqueId) {
synchronized (voicePlayers) {
if(voicePlayers.containsKey(uniqueId)) {
return EnumVoiceState.ENABLED;
}
}
return EnumVoiceState.DISABLED;
}
}

View File

@ -5,14 +5,18 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.pkt.server.SPacketRPCEventToggledVoice;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.EnumVoiceState;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.api.event.EaglercraftVoiceStatusChangeEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerVelocityConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.server.EaglerPlayerData;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.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;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
@ -31,15 +35,13 @@ import net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.config.EaglerVeloci
*/
public class VoiceService {
public static final ChannelIdentifier CHANNEL = new LegacyChannelIdentifier("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(EaglerVelocityConfig 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);
Collection<RegisteredServer> servers = EaglerXVelocity.proxy().getAllServers();
for(RegisteredServer s : servers) {
ServerInfo inf = s.getServerInfo();
@ -49,33 +51,37 @@ public class VoiceService {
}
}
public void handlePlayerLoggedIn(ConnectedPlayer player) {
public void handlePlayerLoggedIn(EaglerPlayerData player) {
}
public void handlePlayerLoggedOut(ConnectedPlayer player) {
public void handlePlayerLoggedOut(EaglerPlayerData player) {
for(VoiceServerImpl svr : serverMap.values()) {
svr.handlePlayerLoggedOut(player);
}
}
public void handleServerConnected(ConnectedPlayer player, ServerInfo server) {
public void handleServerConnected(EaglerPlayerData player, ServerInfo server) {
VoiceServerImpl svr = serverMap.get(server.getName());
if(svr != null) {
svr.handlePlayerLoggedIn(player);
}else {
player.sendPluginMessage(CHANNEL, disableVoicePacket);
player.sendEaglerMessage(disableVoicePacket);
player.fireVoiceStateChange(EaglercraftVoiceStatusChangeEvent.EnumVoiceState.SERVER_DISABLE);
if(player.getRPCEventSubscribed(EnumSubscribedEvent.TOGGLE_VOICE)) {
player.getRPCSessionHandler().handleVoiceStateTransition(SPacketRPCEventToggledVoice.VOICE_STATE_SERVER_DISABLE);
}
}
}
public void handleServerDisconnected(ConnectedPlayer player, ServerInfo server) {
public void handleServerDisconnected(EaglerPlayerData player, ServerInfo server) {
VoiceServerImpl svr = serverMap.get(server.getName());
if(svr != null) {
svr.handlePlayerLoggedOut(player);
}
}
void handleVoiceSignalPacketTypeRequest(UUID player, ConnectedPlayer sender) {
public void handleVoiceSignalPacketTypeRequest(UUID player, EaglerPlayerData sender) {
if(sender.getConnectedServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getConnectedServer().getServerInfo().getName());
if(svr != null) {
@ -84,7 +90,7 @@ public class VoiceService {
}
}
void handleVoiceSignalPacketTypeConnect(ConnectedPlayer sender) {
public void handleVoiceSignalPacketTypeConnect(EaglerPlayerData sender) {
if(sender.getConnectedServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getConnectedServer().getServerInfo().getName());
if(svr != null) {
@ -93,7 +99,7 @@ public class VoiceService {
}
}
void handleVoiceSignalPacketTypeICE(UUID player, String str, ConnectedPlayer sender) {
public void handleVoiceSignalPacketTypeICE(UUID player, byte[] str, EaglerPlayerData sender) {
if(sender.getConnectedServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getConnectedServer().getServerInfo().getName());
if(svr != null) {
@ -102,7 +108,7 @@ public class VoiceService {
}
}
void handleVoiceSignalPacketTypeDesc(UUID player, String str, ConnectedPlayer sender) {
public void handleVoiceSignalPacketTypeDesc(UUID player, byte[] str, EaglerPlayerData sender) {
if(sender.getConnectedServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getConnectedServer().getServerInfo().getName());
if(svr != null) {
@ -111,13 +117,31 @@ public class VoiceService {
}
}
void handleVoiceSignalPacketTypeDisconnect(UUID player, ConnectedPlayer sender) {
public void handleVoiceSignalPacketTypeDisconnect(EaglerPlayerData sender) {
if(sender.getConnectedServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getConnectedServer().getServerInfo().getName());
if(svr != null) {
svr.handleVoiceSignalPacketTypeDisconnect(player, sender);
svr.handleVoiceSignalPacketTypeDisconnect(sender);
}
}
}
public void handleVoiceSignalPacketTypeDisconnectPeer(UUID player, EaglerPlayerData sender) {
if(sender.getConnectedServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getConnectedServer().getServerInfo().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,195 +0,0 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.voice;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.UUID;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
/**
* 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, ConnectedPlayer 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(ProtocolUtils.readUuid(buffer), sender);
break;
}
case VOICE_SIGNAL_CONNECT: {
voiceService.handleVoiceSignalPacketTypeConnect(sender);
break;
}
case VOICE_SIGNAL_ICE: {
voiceService.handleVoiceSignalPacketTypeICE(ProtocolUtils.readUuid(buffer), ProtocolUtils.readString(buffer, 32767), sender);
break;
}
case VOICE_SIGNAL_DESC: {
voiceService.handleVoiceSignalPacketTypeDesc(ProtocolUtils.readUuid(buffer), ProtocolUtils.readString(buffer, 32767), sender);
break;
}
case VOICE_SIGNAL_DISCONNECT: {
voiceService.handleVoiceSignalPacketTypeDisconnect(buffer.readableBytes() > 0 ? ProtocolUtils.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);
ProtocolUtils.writeVarInt(wrappedBuffer, iceServersBytes.length);
for(int i = 0; i < iceServersBytes.length; ++i) {
byte[] b = iceServersBytes[i];
ProtocolUtils.writeVarInt(wrappedBuffer, b.length);
wrappedBuffer.writeBytes(b);
}
return ret;
}
static byte[] makeVoiceSignalPacketGlobal(Collection<ConnectedPlayer> users) {
int cnt = users.size();
byte[][] displayNames = new byte[cnt][];
int i = 0;
for(ConnectedPlayer user : users) {
String name = user.getUsername();
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);
ProtocolUtils.writeVarInt(wrappedBuffer, cnt);
for(ConnectedPlayer user : users) {
ProtocolUtils.writeUuid(wrappedBuffer, user.getUniqueId());
}
for(i = 0; i < cnt; ++i) {
ProtocolUtils.writeVarInt(wrappedBuffer, displayNames[i].length);
wrappedBuffer.writeBytes(displayNames[i]);
}
return ret;
}
static byte[] makeVoiceSignalPacketConnect(UUID player, boolean offer) {
byte[] ret = new byte[18];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_CONNECT);
ProtocolUtils.writeUuid(wrappedBuffer, player);
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);
ProtocolUtils.writeUuid(wrappedBuffer, player);
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);
ProtocolUtils.writeUuid(wrappedBuffer, player);
return ret;
}
static byte[] makeVoiceSignalPacketICE(UUID player, String str) {
byte[] strBytes = str.getBytes(StandardCharsets.UTF_8);
byte[] ret = new byte[17 + getVarIntSize(strBytes.length) + strBytes.length];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_ICE);
ProtocolUtils.writeUuid(wrappedBuffer, player);
ProtocolUtils.writeVarInt(wrappedBuffer, strBytes.length);
wrappedBuffer.writeBytes(strBytes);
return ret;
}
static byte[] makeVoiceSignalPacketDesc(UUID player, String str) {
byte[] strBytes = str.getBytes(StandardCharsets.UTF_8);
byte[] ret = new byte[17 + getVarIntSize(strBytes.length) + strBytes.length];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_DESC);
ProtocolUtils.writeUuid(wrappedBuffer, player);
ProtocolUtils.writeVarInt(wrappedBuffer, strBytes.length);
wrappedBuffer.writeBytes(strBytes);
return ret;
}
public static int getVarIntSize(int input) {
for (int i = 1; i < 5; ++i) {
if ((input & -1 << i * 7) == 0) {
return i;
}
}
return 5;
}
}

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

@ -10,6 +10,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 velocity plugin:
*
* @Subscribe
* 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 +1 @@
{"id":"eaglerxvelocity","name":"EaglercraftXVelocity","version":"1.0.6","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","authors":["lax1dude", "ayunami2000"],"dependencies":[],"main":"net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity"}
{"id":"eaglerxvelocity","name":"EaglercraftXVelocity","version":"1.1.0","description":"Plugin to allow EaglercraftX 1.8 players to join your network, or allow EaglercraftX 1.8 players to use your network as a proxy to join other networks","authors":["lax1dude", "ayunami2000"],"dependencies":[],"main":"net.lax1dude.eaglercraft.v1_8.plugin.gateway_velocity.EaglerXVelocity"}