mirror of
https://github.com/Eaglercraft-Archive/Eaglercraftx-1.8.8-src.git
synced 2025-06-28 10:58:15 -05:00
<1.1.0> Add protocol V4 support to EaglerXVelocity
This commit is contained in:
@ -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();
|
||||
}
|
||||
|
@ -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" };
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 + ")");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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!"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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>
|
@ -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
|
||||
};
|
||||
})();
|
@ -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: ''
|
@ -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>
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@ -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
|
@ -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"}
|
Reference in New Issue
Block a user