Initial Commit

This commit is contained in:
HoosierTransfer 2024-05-09 07:27:22 -04:00
commit 06f03a918c
81 changed files with 12669 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
lib/*
.idea/*
*.iml
out/*

BIN
BungeeCord.jar Normal file

Binary file not shown.

9
readme.txt Normal file
View File

@ -0,0 +1,9 @@
Plugin for eaglercraft on bungeecord
Not using gradle to give more direct access to bungeecord's internals, as gradle only provides a dummy jar containing the api.
EaglercraftXBungee requires netty's websocket client/server, which is already in the production bungeecord jar so it's ideal to compile directly against the real jar
Simply link "src/main/java" and "src/main/resources" as source folders, and then add the latest version of bungeecord jar for minecraft 1.8 to the build path.
To build, export the source folders as a JAR and export the JAR to contain all the classes found in the JARs in "deps" within it, but not including the classes from the actual bungeecord jar

View File

@ -0,0 +1,4 @@
Manifest-Version: 1.0
Main-Class: net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.shit
.MainClass

View File

@ -0,0 +1,321 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.DefaultAuthSystem;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandConfirmCode;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandDomain;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandEaglerPurge;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandEaglerRegister;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command.CommandRatelimit;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerAuthConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers.EaglerPacketEventListener;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers.EaglerPluginEventListener;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerPipeline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.shit.CompatWarning;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.BinaryHttpClient;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.CapeServiceOffline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.ISkinService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinServiceOffline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.BungeeCord;
/**
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglerXBungee extends Plugin {
public static final String NATIVE_BUNGEECORD_BUILD = "1.20-R0.3-SNAPSHOT:67c65e0:1828";
public static final String NATIVE_WATERFALL_BUILD = "1.20-R0.3-SNAPSHOT:da6aaf6:572";
static {
CompatWarning.displayCompatWarning();
}
private static EaglerXBungee instance = null;
private EaglerBungeeConfig conf = null;
private EventLoopGroup eventLoopGroup;
private Collection<Channel> openChannels;
private final Timer closeInactiveConnections;
private Timer skinServiceTasks = null;
private Timer authServiceTasks = null;
private final ChannelFutureListener newChannelListener;
private ISkinService skinService;
private CapeServiceOffline capeService;
private VoiceService voiceService;
private DefaultAuthSystem defaultAuthSystem;
public EaglerXBungee() {
instance = this;
openChannels = new LinkedList();
closeInactiveConnections = new Timer("EaglerXBungee: Network Tick Tasks");
newChannelListener = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture ch) throws Exception {
synchronized(openChannels) { // synchronize whole block to preserve logging order
if(ch.isSuccess()) {
EaglerXBungee.logger().info("Eaglercraft is listening on: " + ch.channel().attr(EaglerPipeline.LOCAL_ADDRESS).get().toString());
openChannels.add(ch.channel());
}else {
EaglerXBungee.logger().severe("Eaglercraft could not bind port: " + ch.channel().attr(EaglerPipeline.LOCAL_ADDRESS).get().toString());
EaglerXBungee.logger().severe("Reason: " + ch.cause().toString());
}
}
}
};
}
@Override
public void onLoad() {
try {
eventLoopGroup = ((BungeeCord) getProxy()).eventLoops;
} catch (NoSuchFieldError e) {
try {
eventLoopGroup = (EventLoopGroup) BungeeCord.class.getField("workerEventLoopGroup").get(getProxy());
} catch (IllegalAccessException | NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
reloadConfig();
closeInactiveConnections.scheduleAtFixedRate(EaglerPipeline.closeInactive, 0l, 250l);
}
@Override
public void onEnable() {
PluginManager mgr = getProxy().getPluginManager();
mgr.registerListener(this, new EaglerPluginEventListener(this));
mgr.registerListener(this, new EaglerPacketEventListener(this));
mgr.registerCommand(this, new CommandRatelimit());
mgr.registerCommand(this, new CommandConfirmCode());
mgr.registerCommand(this, new CommandDomain());
EaglerAuthConfig authConf = conf.getAuthConfig();
if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) {
if(!BungeeCord.getInstance().getConfig().isOnlineMode()) {
getLogger().severe("Online mode is set to false! Authentication system has been disabled");
authConf.triggerOnlineModeDisabled();
}else {
mgr.registerCommand(this, new CommandEaglerRegister(authConf.getEaglerCommandName()));
mgr.registerCommand(this, new CommandEaglerPurge(authConf.getEaglerCommandName()));
}
}
getProxy().registerChannel(SkinService.CHANNEL);
getProxy().registerChannel(CapeServiceOffline.CHANNEL);
getProxy().registerChannel(EaglerPipeline.UPDATE_CERT_CHANNEL);
getProxy().registerChannel(VoiceService.CHANNEL);
getProxy().registerChannel(EaglerPacketEventListener.FNAW_SKIN_ENABLE_CHANNEL);
startListeners();
if(skinServiceTasks != null) {
skinServiceTasks.cancel();
skinServiceTasks = null;
}
boolean downloadSkins = conf.getDownloadVanillaSkins();
if(downloadSkins) {
if(skinService == null) {
skinService = new SkinService();
}else if(skinService instanceof SkinServiceOffline) {
skinService.shutdown();
skinService = new SkinService();
}
} else {
if(skinService == null) {
skinService = new SkinServiceOffline();
}else if(skinService instanceof SkinService) {
skinService.shutdown();
skinService = new SkinServiceOffline();
}
}
skinService.init(conf.getSkinCacheURI(), conf.getSQLiteDriverClass(), conf.getSQLiteDriverPath(),
conf.getKeepObjectsDays(), conf.getKeepProfilesDays(), conf.getMaxObjects(), conf.getMaxProfiles());
if(skinService instanceof SkinService) {
skinServiceTasks = new Timer("EaglerXBungee: Skin Service Tasks");
skinServiceTasks.schedule(new TimerTask() {
@Override
public void run() {
try {
skinService.flush();
}catch(Throwable t) {
logger().log(Level.SEVERE, "Error flushing skin cache!", t);
}
}
}, 1000l, 1000l);
}
capeService = new CapeServiceOffline();
if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) {
try {
defaultAuthSystem = DefaultAuthSystem.initializeAuthSystem(authConf);
}catch(DefaultAuthSystem.AuthSystemException ex) {
logger().log(Level.SEVERE, "Could not load authentication system!", ex);
}
if(defaultAuthSystem != null) {
authServiceTasks = new Timer("EaglerXBungee: Auth Service Tasks");
authServiceTasks.schedule(new TimerTask() {
@Override
public void run() {
try {
defaultAuthSystem.flush();
}catch(Throwable t) {
logger().log(Level.SEVERE, "Error flushing auth cache!", t);
}
}
}, 60000l, 60000l);
}
}
if(conf.getEnableVoiceChat()) {
voiceService = new VoiceService(conf);
logger().warning("Voice chat enabled, not recommended for public servers!");
}else {
logger().info("Voice chat disabled, add \"allow_voice: true\" to your listeners to enable");
}
}
@Override
public void onDisable() {
PluginManager mgr = getProxy().getPluginManager();
mgr.unregisterListeners(this);
mgr.unregisterCommands(this);
getProxy().unregisterChannel(SkinService.CHANNEL);
getProxy().unregisterChannel(CapeServiceOffline.CHANNEL);
getProxy().unregisterChannel(EaglerPipeline.UPDATE_CERT_CHANNEL);
getProxy().unregisterChannel(VoiceService.CHANNEL);
getProxy().unregisterChannel(EaglerPacketEventListener.FNAW_SKIN_ENABLE_CHANNEL);
stopListeners();
if(skinServiceTasks != null) {
skinServiceTasks.cancel();
skinServiceTasks = null;
}
skinService.shutdown();
skinService = null;
capeService.shutdown();
capeService = null;
if(defaultAuthSystem != null) {
defaultAuthSystem.destroy();
defaultAuthSystem = null;
if(authServiceTasks != null) {
authServiceTasks.cancel();
authServiceTasks = null;
}
}
voiceService = null;
BinaryHttpClient.killEventLoop();
}
public void reload() {
stopListeners();
reloadConfig();
startListeners();
}
private void reloadConfig() {
try {
conf = EaglerBungeeConfig.loadConfig(getDataFolder());
if(conf == null) {
throw new IOException("Config failed to parse!");
}
HttpWebServer.regenerate404Pages();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
public void startListeners() {
for(EaglerListenerConfig conf : conf.getServerListeners()) {
if(conf.getAddress() != null) {
makeListener(conf, conf.getAddress());
}
if(conf.getAddressV6() != null) {
makeListener(conf, conf.getAddressV6());
}
}
}
private void makeListener(EaglerListenerConfig confData, InetSocketAddress addr) {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.option(ChannelOption.SO_REUSEADDR, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.channel(PipelineUtils.getServerChannel(addr))
.group(eventLoopGroup)
.childAttr(EaglerPipeline.LISTENER, confData)
.attr(EaglerPipeline.LOCAL_ADDRESS, addr)
.localAddress(addr)
.childHandler(EaglerPipeline.SERVER_CHILD)
.bind().addListener(newChannelListener);
}
public void stopListeners() {
synchronized(openChannels) {
for(Channel c : openChannels) {
c.close().syncUninterruptibly();
EaglerXBungee.logger().info("Eaglercraft listener closed: " + c.attr(EaglerPipeline.LOCAL_ADDRESS).get().toString());
}
openChannels.clear();
}
synchronized(EaglerPipeline.openChannels) {
EaglerPipeline.openChannels.clear();
}
}
public EaglerBungeeConfig getConfig() {
return conf;
}
public EventLoopGroup getEventLoopGroup() {
return eventLoopGroup;
}
public ISkinService getSkinService() {
return skinService;
}
public CapeServiceOffline getCapeService() {
return capeService;
}
public DefaultAuthSystem getAuthService() {
return defaultAuthSystem;
}
public VoiceService getVoiceService() {
return voiceService;
}
public static EaglerXBungee getEagler() {
return instance;
}
public static Logger logger() {
return instance.getLogger();
}
}

View File

@ -0,0 +1,196 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.net.InetAddress;
import java.util.UUID;
import java.util.function.Consumer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.md_5.bungee.api.plugin.Event;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglercraftHandleAuthPasswordEvent extends Event {
public static enum AuthResponse {
ALLOW, DENY
}
private final EaglerListenerConfig listener;
private final InetAddress authRemoteAddress;
private final String authOrigin;
private final byte[] authUsername;
private final byte[] authSaltingData;
private final byte[] authPasswordData;
private final EaglercraftIsAuthRequiredEvent.AuthMethod eventAuthMethod;
private final String eventAuthMessage;
private final Object authAttachment;
private AuthResponse eventResponse;
private CharSequence authProfileUsername;
private UUID authProfileUUID;
private String authRequestedServerRespose;
private String authDeniedMessage = "Password Incorrect!";
private String applyTexturesPropValue;
private String applyTexturesPropSignature;
private boolean overrideEaglerToVanillaSkins;
private Consumer<EaglercraftHandleAuthPasswordEvent> continueThread;
private Runnable continueRunnable;
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) {
this.listener = listener;
this.authRemoteAddress = authRemoteAddress;
this.authOrigin = authOrigin;
this.authUsername = authUsername;
this.authSaltingData = authSaltingData;
this.authProfileUsername = authProfileUsername;
this.authProfileUUID = authProfileUUID;
this.authPasswordData = authPasswordData;
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 byte[] getAuthUsername() {
return authUsername;
}
public byte[] getAuthSaltingData() {
return authSaltingData;
}
public CharSequence getProfileUsername() {
return authProfileUsername;
}
public void setProfileUsername(CharSequence username) {
this.authProfileUsername = username;
}
public UUID getProfileUUID() {
return authProfileUUID;
}
public void setProfileUUID(UUID uuid) {
this.authProfileUUID = uuid;
}
public byte[] getAuthPasswordDataResponse() {
return authPasswordData;
}
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 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(EaglercraftHandleAuthPasswordEvent.this);
}else {
throw new IllegalStateException("Thread was already continued from a different function! Auth plugin conflict?");
}
}
};
}
return continueRunnable;
}
public boolean isAsyncContinue() {
return continueRunnable != null;
}
public void doDirectContinue() {
continueThread.accept(this);
}
public void applyTexturesProperty(String value, String signature) {
applyTexturesPropValue = value;
applyTexturesPropSignature = signature;
}
public String getApplyTexturesPropertyValue() {
return applyTexturesPropValue;
}
public String getApplyTexturesPropertySignature() {
return applyTexturesPropSignature;
}
public void setOverrideEaglerToVanillaSkins(boolean overrideEaglerToVanillaSkins) {
this.overrideEaglerToVanillaSkins = overrideEaglerToVanillaSkins;
}
public boolean isOverrideEaglerToVanillaSkins() {
return overrideEaglerToVanillaSkins;
}
}

View File

@ -0,0 +1,158 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.net.InetAddress;
import java.util.function.Consumer;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.md_5.bungee.api.plugin.Event;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglercraftIsAuthRequiredEvent extends Event {
public static enum AuthResponse {
SKIP, REQUIRE, DENY
}
public static enum AuthMethod {
PLAINTEXT, EAGLER_SHA256, AUTHME_SHA256
}
public EaglercraftIsAuthRequiredEvent(EaglerListenerConfig listener, InetAddress authRemoteAddress,
String authOrigin, boolean wantsAuth, byte[] authUsername,
Consumer<EaglercraftIsAuthRequiredEvent> continueThread) {
this.listener = listener;
this.authRemoteAddress = authRemoteAddress;
this.authOrigin = authOrigin;
this.wantsAuth = wantsAuth;
this.authUsername = authUsername;
this.continueThread = continueThread;
}
private final EaglerListenerConfig listener;
private AuthResponse authResponse;
private final InetAddress authRemoteAddress;
private final String authOrigin;
private final boolean wantsAuth;
private final byte[] authUsername;
private byte[] authSaltingData;
private AuthMethod eventAuthMethod = null;
private String eventAuthMessage = "enter the code:";
private String kickUserMessage = "Login Denied";
private Object authAttachment;
private Consumer<EaglercraftIsAuthRequiredEvent> continueThread;
private Runnable continueRunnable;
private volatile boolean hasContinue = false;
public EaglerListenerConfig getListener() {
return listener;
}
public InetAddress getRemoteAddress() {
return authRemoteAddress;
}
public String getOriginHeader() {
return authOrigin;
}
public boolean isClientSolicitingPasscode() {
return wantsAuth;
}
public byte[] getAuthUsername() {
return authUsername;
}
public byte[] getSaltingData() {
return authSaltingData;
}
public void setSaltingData(byte[] saltingData) {
authSaltingData = saltingData;
}
public AuthMethod getUseAuthType() {
return eventAuthMethod;
}
public void setUseAuthMethod(AuthMethod authMethod) {
this.eventAuthMethod = authMethod;
}
public AuthResponse getAuthRequired() {
return authResponse;
}
public void setAuthRequired(AuthResponse required) {
this.authResponse = required;
}
public String getAuthMessage() {
return eventAuthMessage;
}
public void setAuthMessage(String eventAuthMessage) {
this.eventAuthMessage = eventAuthMessage;
}
public <T> T getAuthAttachment() {
return (T)authAttachment;
}
public void setAuthAttachment(Object authAttachment) {
this.authAttachment = authAttachment;
}
public boolean shouldKickUser() {
return authResponse == null || authResponse == AuthResponse.DENY;
}
public String getKickMessage() {
return kickUserMessage;
}
public void kickUser(String message) {
authResponse = AuthResponse.DENY;
kickUserMessage = message;
}
public Runnable makeAsyncContinue() {
if(continueRunnable == null) {
continueRunnable = new Runnable() {
@Override
public void run() {
if(!hasContinue) {
hasContinue = true;
continueThread.accept(EaglercraftIsAuthRequiredEvent.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);
}
}

View File

@ -0,0 +1,48 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.net.InetAddress;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.MOTDConnection;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.md_5.bungee.api.plugin.Event;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglercraftMOTDEvent extends Event {
protected final MOTDConnection connection;
public EaglercraftMOTDEvent(MOTDConnection connection) {
this.connection = connection;
}
public InetAddress getRemoteAddress() {
return connection.getAddress();
}
public EaglerListenerConfig getListener() {
return connection.getListener();
}
public String getAccept() {
return connection.getAccept();
}
public MOTDConnection getConnection() {
return connection;
}
}

View File

@ -0,0 +1,118 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event;
import java.util.UUID;
import net.md_5.bungee.api.plugin.Event;
import net.md_5.bungee.protocol.Property;
/**
* 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 EaglercraftRegisterSkinEvent extends Event {
private final String username;
private final UUID uuid;
private Property useMojangProfileProperty = null;
private boolean useLoginResultTextures = false;
private int presetId = -1;
private byte[] customTex = null;
private String customURL = null;
public EaglercraftRegisterSkinEvent(String username, UUID uuid) {
this.username = username;
this.uuid = uuid;
}
public void setForceUseMojangProfileProperty(Property prop) {
useMojangProfileProperty = prop;
useLoginResultTextures = false;
presetId = -1;
customTex = null;
customURL = null;
}
public void setForceUseLoginResultObjectTextures(boolean b) {
useMojangProfileProperty = null;
useLoginResultTextures = b;
presetId = -1;
customTex = null;
customURL = null;
}
public void setForceUsePreset(int p) {
useMojangProfileProperty = null;
useLoginResultTextures = false;
presetId = p;
customTex = new byte[5];
customTex[0] = (byte)1;
customTex[1] = (byte)(p >> 24);
customTex[2] = (byte)(p >> 16);
customTex[3] = (byte)(p >> 8);
customTex[4] = (byte)(p & 0xFF);
customURL = null;
}
public void setForceUseCustom(int model, byte[] tex) {
useMojangProfileProperty = null;
useLoginResultTextures = false;
presetId = -1;
customTex = new byte[2 + tex.length];
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;
presetId = -1;
customTex = packet;
customURL = null;
}
public void setForceUseURL(String url) {
useMojangProfileProperty = null;
useLoginResultTextures = false;
presetId = -1;
customTex = null;
customURL = url;
}
public String getUsername() {
return username;
}
public UUID getUuid() {
return uuid;
}
public Property getForceUseMojangProfileProperty() {
return useMojangProfileProperty;
}
public boolean getForceUseLoginResultObjectTextures() {
return useLoginResultTextures;
}
public byte[] getForceSetUseCustomPacket() {
return customTex;
}
public String getForceSetUseURL() {
return customURL;
}
}

View File

@ -0,0 +1,31 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.HttpServerQueryHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.QueryManager;
/**
* 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 abstract class EaglerQueryHandler extends HttpServerQueryHandler {
public static void registerQueryType(String name, Class<? extends EaglerQueryHandler> clazz) {
QueryManager.registerQueryType(name, clazz);
}
public static void unregisterQueryType(String name) {
QueryManager.unregisterQueryType(name);
}
}

View File

@ -0,0 +1,61 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query;
import com.google.gson.JsonObject;
/**
* 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 abstract class EaglerQuerySimpleHandler extends EaglerQueryHandler {
@Override
protected void processString(String str) {
throw new UnexpectedDataException();
}
@Override
protected void processJson(JsonObject obj) {
throw new UnexpectedDataException();
}
@Override
protected void processBytes(byte[] bytes) {
throw new UnexpectedDataException();
}
@Override
protected void acceptText() {
throw new UnsupportedOperationException("EaglerQuerySimpleHandler does not support duplex");
}
@Override
protected void acceptText(boolean bool) {
throw new UnsupportedOperationException("EaglerQuerySimpleHandler does not support duplex");
}
@Override
protected void acceptBinary() {
throw new UnsupportedOperationException("EaglerQuerySimpleHandler does not support duplex");
}
@Override
protected void acceptBinary(boolean bool) {
throw new UnsupportedOperationException("EaglerQuerySimpleHandler does not support duplex");
}
@Override
protected void closed() {
}
}

View File

@ -0,0 +1,55 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query;
import java.net.InetAddress;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.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 interface MOTDConnection {
boolean isClosed();
void close();
String getAccept();
InetAddress getAddress();
EaglerListenerConfig getListener();
long getConnectionTimestamp();
public default long getConnectionAge() {
return System.currentTimeMillis() - getConnectionTimestamp();
}
void sendToUser();
String getLine1();
String getLine2();
List<String> getPlayerList();
int[] getBitmap();
int getOnlinePlayers();
int getMaxPlayers();
String getSubType();
void setLine1(String p);
void setLine2(String p);
void setPlayerList(List<String> p);
void setPlayerList(String... p);
void setBitmap(int[] p);
void setOnlinePlayers(int i);
void setMaxPlayers(int i);
}

View File

@ -0,0 +1,114 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
/**
* 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 AuthLoadingCache<K, V> {
private static class CacheEntry<V> {
private long lastHit;
private V instance;
private CacheEntry(V instance) {
this.lastHit = System.currentTimeMillis();
this.instance = instance;
}
}
public static interface CacheLoader<K, V> {
V load(K key);
}
public static interface CacheVisitor<K, V> {
boolean shouldEvict(K key, V value);
}
private final Map<K, CacheEntry<V>> cacheMap;
private final CacheLoader<K, V> provider;
private final long cacheTTL;
private long cacheTimer;
public AuthLoadingCache(CacheLoader<K, V> provider, long cacheTTL) {
this.cacheMap = new HashMap();
this.provider = provider;
this.cacheTTL = cacheTTL;
}
public V get(K key) {
CacheEntry<V> etr;
synchronized(cacheMap) {
etr = cacheMap.get(key);
}
if(etr == null) {
V loaded = provider.load(key);
synchronized(cacheMap) {
cacheMap.put(key, new CacheEntry<>(loaded));
}
return loaded;
}else {
etr.lastHit = System.currentTimeMillis();
return etr.instance;
}
}
public void evict(K key) {
synchronized(cacheMap) {
cacheMap.remove(key);
}
}
public void evictAll(CacheVisitor<K, V> visitor) {
synchronized(cacheMap) {
Iterator<Entry<K,CacheEntry<V>>> itr = cacheMap.entrySet().iterator();
while(itr.hasNext()) {
Entry<K,CacheEntry<V>> etr = itr.next();
if(visitor.shouldEvict(etr.getKey(), etr.getValue().instance)) {
itr.remove();
}
}
}
}
public void tick() {
long millis = System.currentTimeMillis();
if(millis - cacheTimer > (cacheTTL / 2L)) {
cacheTimer = millis;
synchronized(cacheMap) {
Iterator<CacheEntry<V>> mapItr = cacheMap.values().iterator();
while(mapItr.hasNext()) {
CacheEntry<V> etr = mapItr.next();
if(millis - etr.lastHit > cacheTTL) {
mapItr.remove();
}
}
}
}
}
public void flush() {
synchronized(cacheMap) {
cacheMap.clear();
}
}
}

View File

@ -0,0 +1,680 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Properties;
import java.util.Random;
import java.util.UUID;
import java.util.logging.Level;
import org.apache.commons.codec.binary.Base64;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftHandleAuthPasswordEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftIsAuthRequiredEvent;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftIsAuthRequiredEvent.AuthMethod;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.event.EaglercraftIsAuthRequiredEvent.AuthResponse;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerAuthConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.sqlite.EaglerDrivers;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.Property;
/**
* 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 DefaultAuthSystem {
public static class AuthSystemException extends RuntimeException {
public AuthSystemException() {
}
public AuthSystemException(String message, Throwable cause) {
super(message, cause);
}
public AuthSystemException(String message) {
super(message);
}
public AuthSystemException(Throwable cause) {
super(cause);
}
}
protected final String uri;
protected final Connection databaseConnection;
protected final String passwordPromptScreenText;
protected final String wrongPasswordScreenText;
protected final String notRegisteredScreenText;
protected final String eaglerCommandName;
protected final String useRegisterCommandText;
protected final String useChangeCommandText;
protected final String commandSuccessText;
protected final String lastEaglerLoginMessage;
protected final String tooManyRegistrationsMessage;
protected final String needVanillaToRegisterMessage;
protected final boolean overrideEaglerToVanillaSkins;
protected final int maxRegistrationsPerIP;
protected final SecureRandom secureRandom;
public static DefaultAuthSystem initializeAuthSystem(EaglerAuthConfig config) throws AuthSystemException {
String databaseURI = config.getDatabaseURI();
Connection conn;
try {
conn = EaglerDrivers.connectToDatabase(databaseURI, config.getDriverClass(), config.getDriverPath(), new Properties());
if(conn == null) {
throw new IllegalStateException("Connection is null");
}
}catch(Throwable t) {
throw new AuthSystemException("Could not initialize '" + databaseURI + "'!", t);
}
EaglerXBungee.logger().info("Connected to database: " + databaseURI);
try {
try(Statement stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE IF NOT EXISTS \"eaglercraft_accounts\" ("
+ "\"Version\" TINYINT NOT NULL,"
+ "\"MojangUUID\" TEXT(32) NOT NULL,"
+ "\"MojangUsername\" TEXT(16) NOT NULL,"
+ "\"HashBase\" BLOB NOT NULL,"
+ "\"HashSalt\" BLOB NOT NULL,"
+ "\"MojangTextures\" BLOB,"
+ "\"Registered\" DATETIME NOT NULL,"
+ "\"RegisteredIP\" VARCHAR(42) NOT NULL,"
+ "\"LastLogin\" DATETIME,"
+ "\"LastLoginIP\" VARCHAR(42),"
+ "PRIMARY KEY(\"MojangUUID\"))");
stmt.execute("CREATE UNIQUE INDEX IF NOT EXISTS \"MojangUsername\" ON "
+ "\"eaglercraft_accounts\" (\"MojangUsername\")");
}
return new DefaultAuthSystem(databaseURI, conn, config.getPasswordPromptScreenText(),
config.getWrongPasswordScreenText(), config.getNotRegisteredScreenText(),
config.getEaglerCommandName(), config.getUseRegisterCommandText(), config.getUseChangeCommandText(),
config.getCommandSuccessText(), config.getLastEaglerLoginMessage(),
config.getTooManyRegistrationsMessage(), config.getNeedVanillaToRegisterMessage(),
config.getOverrideEaglerToVanillaSkins(), config.getMaxRegistrationsPerIP());
}catch(AuthSystemException ex) {
try {
conn.close();
}catch(SQLException exx) {
}
throw ex;
}catch(Throwable t) {
try {
conn.close();
}catch(SQLException exx) {
}
throw new AuthSystemException("Could not initialize '" + databaseURI + "'!", t);
}
}
protected final PreparedStatement registerUser;
protected final PreparedStatement isRegisteredUser;
protected final PreparedStatement pruneUsers;
protected final PreparedStatement updatePassword;
protected final PreparedStatement updateMojangUsername;
protected final PreparedStatement getRegistrationsOnIP;
protected final PreparedStatement checkRegistrationByUUID;
protected final PreparedStatement checkRegistrationByName;
protected final PreparedStatement setLastLogin;
protected final PreparedStatement updateTextures;
protected class AccountLoader implements AuthLoadingCache.CacheLoader<String, CachedAccountInfo> {
@Override
public CachedAccountInfo load(String key) {
try {
CachedAccountInfo cachedInfo = null;
synchronized(checkRegistrationByName) {
checkRegistrationByName.setString(1, key);
try(ResultSet res = checkRegistrationByName.executeQuery()) {
if (res.next()) {
cachedInfo = new CachedAccountInfo(res.getInt(1), parseMojangUUID(res.getString(2)), key,
res.getBytes(3), res.getBytes(4), res.getBytes(5), res.getDate(6), res.getString(7),
res.getDate(8), res.getString(9));
}
}
}
return cachedInfo;
}catch(SQLException ex) {
throw new AuthException("Failed to query database!", ex);
}
}
}
protected class CachedAccountInfo {
protected int version;
protected UUID mojangUUID;
protected String mojangUsername;
protected byte[] texturesProperty;
protected byte[] hashBase;
protected byte[] hashSalt;
protected long registered;
protected String registeredIP;
protected long lastLogin;
protected String lastLoginIP;
protected CachedAccountInfo(int version, UUID mojangUUID, String mojangUsername, byte[] texturesProperty,
byte[] hashBase, byte[] hashSalt, Date registered, String registeredIP, Date lastLogin,
String lastLoginIP) {
this(version, mojangUUID, mojangUsername, texturesProperty, hashBase, hashSalt,
registered == null ? 0l : registered.getTime(), registeredIP,
lastLogin == null ? 0l : lastLogin.getTime(), lastLoginIP);
}
protected CachedAccountInfo(int version, UUID mojangUUID, String mojangUsername, byte[] texturesProperty,
byte[] hashBase, byte[] hashSalt, long registered, String registeredIP, long lastLogin,
String lastLoginIP) {
this.version = version;
this.mojangUUID = mojangUUID;
this.mojangUsername = mojangUsername;
this.texturesProperty = texturesProperty;
this.hashBase = hashBase;
this.hashSalt = hashSalt;
this.registered = registered;
this.registeredIP = registeredIP;
this.lastLogin = lastLogin;
this.lastLoginIP = lastLoginIP;
}
}
protected final AuthLoadingCache<String, CachedAccountInfo> authLoadingCache;
protected DefaultAuthSystem(String uri, Connection databaseConnection, String passwordPromptScreenText,
String wrongPasswordScreenText, String notRegisteredScreenText, String eaglerCommandName,
String useRegisterCommandText, String useChangeCommandText, String commandSuccessText,
String lastEaglerLoginMessage, String tooManyRegistrationsMessage, String needVanillaToRegisterMessage,
boolean overrideEaglerToVanillaSkins, int maxRegistrationsPerIP) throws SQLException {
this.uri = uri;
this.databaseConnection = databaseConnection;
this.passwordPromptScreenText = passwordPromptScreenText;
this.wrongPasswordScreenText = wrongPasswordScreenText;
this.notRegisteredScreenText = notRegisteredScreenText;
this.eaglerCommandName = eaglerCommandName;
this.useRegisterCommandText = useRegisterCommandText;
this.useChangeCommandText = useChangeCommandText;
this.commandSuccessText = commandSuccessText;
this.lastEaglerLoginMessage = lastEaglerLoginMessage;
this.tooManyRegistrationsMessage = tooManyRegistrationsMessage;
this.needVanillaToRegisterMessage = needVanillaToRegisterMessage;
this.overrideEaglerToVanillaSkins = overrideEaglerToVanillaSkins;
this.maxRegistrationsPerIP = maxRegistrationsPerIP;
this.registerUser = databaseConnection.prepareStatement("INSERT INTO eaglercraft_accounts (Version, MojangUUID, MojangUsername, MojangTextures, HashBase, HashSalt, Registered, RegisteredIP) VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
this.isRegisteredUser = databaseConnection.prepareStatement("SELECT COUNT(MojangUUID) AS total_accounts FROM eaglercraft_accounts WHERE MojangUUID = ?");
this.pruneUsers = databaseConnection.prepareStatement("DELETE FROM eaglercraft_accounts WHERE LastLogin < ?");
this.updatePassword = databaseConnection.prepareStatement("UPDATE eaglercraft_accounts SET HashBase = ?, HashSalt = ? WHERE MojangUUID = ?");
this.updateMojangUsername = databaseConnection.prepareStatement("UPDATE eaglercraft_accounts SET MojangUsername = ? WHERE MojangUUID = ?");
this.getRegistrationsOnIP = databaseConnection.prepareStatement("SELECT COUNT(MojangUUID) AS total_accounts FROM eaglercraft_accounts WHERE RegisteredIP = ?");
this.checkRegistrationByUUID = databaseConnection.prepareStatement("SELECT Version, MojangUsername, LastLogin, LastLoginIP FROM eaglercraft_accounts WHERE MojangUUID = ?");
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.secureRandom = new SecureRandom();
}
public void handleIsAuthRequiredEvent(EaglercraftIsAuthRequiredEvent event) {
String username = new String(event.getAuthUsername(), StandardCharsets.US_ASCII);
String usrs = username.toString();
if(!usrs.equals(usrs.replaceAll("[^A-Za-z0-9_]", "_").trim())) {
event.kickUser("Invalid characters in username");
return;
}
if(username.length() < 3) {
event.kickUser("Username must be at least 3 characters");
return;
}
if(username.length() > 16) {
event.kickUser("Username must be under 16 characters");
return;
}
CachedAccountInfo info = authLoadingCache.get(username);
if(info == null) {
event.kickUser(notRegisteredScreenText);
return;
}
event.setAuthAttachment(info);
event.setAuthRequired(AuthResponse.REQUIRE);
event.setAuthMessage(passwordPromptScreenText);
event.setUseAuthMethod(AuthMethod.EAGLER_SHA256);
byte[] randomBytes = new byte[32];
Random rng;
synchronized(secureRandom) {
rng = new Random(secureRandom.nextLong());
}
rng.nextBytes(randomBytes);
byte[] saltingData = new byte[64];
System.arraycopy(info.hashSalt, 0, saltingData, 0, 32);
System.arraycopy(randomBytes, 0, saltingData, 32, 32);
event.setSaltingData(saltingData);
}
public void handleAuthPasswordEvent(EaglercraftHandleAuthPasswordEvent event) {
CachedAccountInfo info = event.getAuthAttachment();
if(info == null) {
event.setLoginDenied(notRegisteredScreenText);
return;
}
byte[] responseHash = event.getAuthPasswordDataResponse();
if(responseHash.length != 32) {
event.setLoginDenied("Wrong number of bits in checksum!");
return;
}
byte[] saltingData = event.getAuthSaltingData();
SHA256Digest digest = new SHA256Digest();
digest.update(info.hashBase, 0, 32);
digest.update(saltingData, 32, 32);
digest.update(HashUtils.EAGLER_SHA256_SALT_BASE, 0, 32);
byte[] hashed = new byte[32];
digest.doFinal(hashed, 0);
if(!Arrays.equals(hashed, responseHash)) {
event.setLoginDenied(wrongPasswordScreenText);
EaglerXBungee.logger().warning("User \"" + info.mojangUsername + "\" entered the wrong password while logging in from: " + event.getRemoteAddress().getHostAddress());
return;
}
try {
synchronized(setLastLogin) {
setLastLogin.setDate(1, new Date(System.currentTimeMillis()));
setLastLogin.setString(2, event.getRemoteAddress().getHostAddress());
setLastLogin.setString(3, getMojangUUID(info.mojangUUID));
if(setLastLogin.executeUpdate() == 0) {
throw new SQLException("Query did not alter the database");
}
}
}catch(SQLException ex) {
EaglerXBungee.logger().log(Level.SEVERE, "Could not update last login for \"" + info.mojangUUID.toString() + "\"", ex);
}
event.setLoginAllowed();
event.setProfileUsername(info.mojangUsername);
event.setProfileUUID(info.mojangUUID);
byte[] texturesProp = info.texturesProperty;
if(texturesProp != null) {
try {
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(texturesProp));
int valueLen = dis.readInt();
int sigLen = dis.readInt();
byte[] valueBytes = new byte[valueLen];
dis.read(valueBytes);
String valueB64 = Base64.encodeBase64String(valueBytes);
String sigB64 = null;
if(sigLen > 0) {
valueBytes = new byte[sigLen];
dis.read(valueBytes);
sigB64 = Base64.encodeBase64String(valueBytes);
}
event.applyTexturesProperty(valueB64, sigB64);
event.setOverrideEaglerToVanillaSkins(overrideEaglerToVanillaSkins);
}catch(IOException ex) {
}
}
}
public void processSetPassword(ProxiedPlayer player, String password) throws TooManyRegisteredOnIPException, AuthException {
PendingConnection conn = player.getPendingConnection();
if(conn instanceof EaglerInitialHandler) {
throw new AuthException("Cannot register from an eaglercraft account!");
}else if(!conn.isOnlineMode()) {
throw new AuthException("Cannot register without online mode enabled!");
}else {
try {
String uuid = getMojangUUID(player.getUniqueId());
synchronized(registerUser) {
int cnt;
synchronized(isRegisteredUser) {
isRegisteredUser.setString(1, uuid);
try(ResultSet set = isRegisteredUser.executeQuery()) {
if(set.next()) {
cnt = set.getInt(1);
}else {
throw new SQLException("Empty ResultSet recieved while checking if user exists");
}
}
}
SHA256Digest digest = new SHA256Digest();
int passLen = password.length();
digest.update((byte)((passLen >> 8) & 0xFF));
digest.update((byte)(passLen & 0xFF));
for(int i = 0; i < passLen; ++i) {
char codePoint = password.charAt(i);
digest.update((byte)((codePoint >> 8) & 0xFF));
digest.update((byte)(codePoint & 0xFF));
}
digest.update(HashUtils.EAGLER_SHA256_SALT_SAVE, 0, 32);
byte[] hashed = new byte[32];
digest.doFinal(hashed, 0);
byte[] randomBytes = new byte[32];
synchronized(secureRandom) {
secureRandom.nextBytes(randomBytes);
}
digest.reset();
digest.update(hashed, 0, 32);
digest.update(randomBytes, 0, 32);
digest.update(HashUtils.EAGLER_SHA256_SALT_BASE, 0, 32);
digest.doFinal(hashed, 0);
String username = player.getName();
authLoadingCache.evict(username);
if(cnt > 0) {
synchronized(updatePassword) {
updatePassword.setBytes(1, hashed);
updatePassword.setBytes(2, randomBytes);
updatePassword.setString(3, uuid);
if(updatePassword.executeUpdate() <= 0) {
throw new AuthException("Update password query did not alter the database!");
}
}
}else {
String sockAddr = sockAddrToString(player.getSocketAddress());
if(maxRegistrationsPerIP > 0) {
if(countUsersOnIP(sockAddr) >= maxRegistrationsPerIP) {
throw new TooManyRegisteredOnIPException(sockAddr);
}
}
Date nowDate = new Date(System.currentTimeMillis());
registerUser.setInt(1, 1);
registerUser.setString(2, uuid);
registerUser.setString(3, username);
LoginResult res = ((InitialHandler)player.getPendingConnection()).getLoginProfile();
if(res != null) {
registerUser.setBytes(4, getTexturesProperty(res));
}else {
registerUser.setBytes(4, null);
}
registerUser.setBytes(5, hashed);
registerUser.setBytes(6, randomBytes);
registerUser.setDate(7, nowDate);
registerUser.setString(8, sockAddr);
if(registerUser.executeUpdate() <= 0) {
throw new AuthException("Registration query did not alter the database!");
}
}
}
}catch(SQLException ex) {
throw new AuthException("Failed to query database!", ex);
}
}
}
private static byte[] getTexturesProperty(LoginResult profile) {
try {
Property[] props = profile.getProperties();
for(int i = 0; i < props.length; ++i) {
Property prop = props[i];
if("textures".equals(prop.getName())) {
byte[] texturesData = Base64.decodeBase64(prop.getValue());
byte[] signatureData = prop.getSignature() == null ? new byte[0] : Base64.decodeBase64(prop.getSignature());
ByteArrayOutputStream bao = new ByteArrayOutputStream();
DataOutputStream dao = new DataOutputStream(bao);
dao.writeInt(texturesData.length);
dao.writeInt(signatureData.length);
dao.write(texturesData);
dao.write(signatureData);
return bao.toByteArray();
}
}
}catch(Throwable t) {
}
return null;
}
public int pruneUsers(long before) throws AuthException {
try {
authLoadingCache.flush();
synchronized(pruneUsers) {
pruneUsers.setDate(1, new Date(before));
return pruneUsers.executeUpdate();
}
}catch(SQLException ex) {
throw new AuthException("Failed to query database!", ex);
}
}
public int countUsersOnIP(String addr) throws AuthException {
synchronized(getRegistrationsOnIP) {
try {
getRegistrationsOnIP.setString(1, addr);
try(ResultSet set = getRegistrationsOnIP.executeQuery()) {
if(set.next()) {
return set.getInt(1);
}else {
throw new SQLException("Empty ResultSet recieved while counting accounts");
}
}
}catch(SQLException ex) {
throw new AuthException("Failed to query database!", ex);
}
}
}
public void handleVanillaLogin(PostLoginEvent loginEvent) {
ProxiedPlayer player = loginEvent.getPlayer();
PendingConnection con = player.getPendingConnection();
if(!(con instanceof EaglerInitialHandler)) {
Date lastLogin = null;
String lastLoginIP = null;
boolean isRegistered = false;
synchronized(checkRegistrationByUUID) {
UUID uuid = player.getUniqueId();
try {
String uuidString = getMojangUUID(uuid);
checkRegistrationByUUID.setString(1, getMojangUUID(player.getUniqueId()));
try(ResultSet res = checkRegistrationByUUID.executeQuery()) {
if(res.next()) {
isRegistered = true;
int vers = res.getInt(1);
String username = res.getString(2);
lastLogin = res.getDate(3);
lastLoginIP = res.getString(4);
String playerName = player.getName();
if(!playerName.equals(username)) {
EaglerXBungee.logger().info("Player \"" + uuid.toString() + "\" changed their username from \"" + username
+ " to \"" + playerName + "\", updating authentication database...");
synchronized(updateMojangUsername) {
updateMojangUsername.setString(1, playerName);
updateMojangUsername.setString(2, uuidString);
if(updateMojangUsername.executeUpdate() == 0) {
throw new SQLException("Failed to update username to \"" + playerName + "\"");
}
}
}
}
}
byte[] texProperty = getTexturesProperty(((InitialHandler)con).getLoginProfile());
if(texProperty != null) {
synchronized(updateTextures) {
updateTextures.setBytes(1, texProperty);
updateTextures.setString(2, uuidString);
updateTextures.executeUpdate();
}
}
}catch(SQLException ex) {
EaglerXBungee.logger().log(Level.SEVERE, "Could not look up UUID \"" + uuid.toString() + "\" in auth database!", ex);
}
}
if(isRegistered) {
if(lastLogin != null) {
String dateStr;
java.util.Date juLastLogin = new java.util.Date(lastLogin.getTime());
Calendar calendar = Calendar.getInstance();
int yearToday = calendar.get(Calendar.YEAR);
calendar.setTime(juLastLogin);
if(calendar.get(Calendar.YEAR) != yearToday) {
dateStr = (new SimpleDateFormat("EE, MMM d, yyyy, HH:mm z")).format(juLastLogin);
}else {
dateStr = (new SimpleDateFormat("EE, MMM d, HH:mm z")).format(juLastLogin);
}
TextComponent comp = new TextComponent(lastEaglerLoginMessage.replace("$date", dateStr).replace("$ip", "" + lastLoginIP));
comp.setColor(ChatColor.GREEN);
player.sendMessage(comp);
}
player.sendMessage(new TextComponent(useChangeCommandText));
}else {
player.sendMessage(new TextComponent(useRegisterCommandText));
}
}
}
private void destroyStatement(Statement stmt) {
try {
stmt.close();
} catch (SQLException e) {
}
}
public void flush() {
authLoadingCache.flush();
}
public void destroy() {
destroyStatement(registerUser);
destroyStatement(isRegisteredUser);
destroyStatement(pruneUsers);
destroyStatement(updatePassword);
destroyStatement(updateMojangUsername);
destroyStatement(getRegistrationsOnIP);
destroyStatement(checkRegistrationByUUID);
destroyStatement(checkRegistrationByName);
destroyStatement(setLastLogin);
destroyStatement(updateTextures);
try {
databaseConnection.close();
EaglerXBungee.logger().info("Successfully disconnected from database '" + uri + "'");
} catch (SQLException e) {
EaglerXBungee.logger().log(Level.WARNING, "Exception disconnecting from database '" + uri + "'!", e);
}
}
public static class AuthException extends RuntimeException {
public AuthException(String msg) {
super(msg);
}
public AuthException(Throwable t) {
super(t);
}
public AuthException(String msg, Throwable t) {
super(msg, t);
}
}
public static class TooManyRegisteredOnIPException extends AuthException {
public TooManyRegisteredOnIPException(String ip) {
super(ip);
}
}
private static final String hexString = "0123456789abcdef";
private static final char[] HEX = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
public static String getMojangUUID(UUID uuid) {
char[] ret = new char[32];
long msb = uuid.getMostSignificantBits();
long lsb = uuid.getLeastSignificantBits();
for(int i = 0, j; i < 16; ++i) {
j = (15 - i) << 2;
ret[i] = HEX[(int)((msb >> j) & 15l)];
ret[i + 16] = HEX[(int)((lsb >> j) & 15l)];
}
return new String(ret);
}
public static UUID parseMojangUUID(String uuid) {
long msb = 0l;
long lsb = 0l;
for(int i = 0, j; i < 16; ++i) {
j = (15 - i) << 2;
msb |= ((long)hexString.indexOf(uuid.charAt(i)) << j);
lsb |= ((long)hexString.indexOf(uuid.charAt(i + 16)) << j);
}
return new UUID(msb, lsb);
}
private static String sockAddrToString(SocketAddress addr) {
if(addr instanceof InetSocketAddress) {
return ((InetSocketAddress)addr).getAddress().getHostAddress();
}else {
return "127.0.0.1";
}
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth;
/**
* base implementation of MD4 family style digest as outlined in "Handbook of
* Applied Cryptography", pages 344 - 347.
*/
public abstract class GeneralDigest {
private byte[] xBuf;
private int xBufOff;
private long byteCount;
/**
* Standard constructor
*/
protected GeneralDigest() {
xBuf = new byte[4];
xBufOff = 0;
}
/**
* Copy constructor. We are using copy constructors in place of the
* Object.clone() interface as this interface is not supported by J2ME.
*/
protected GeneralDigest(GeneralDigest t) {
xBuf = new byte[t.xBuf.length];
System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length);
xBufOff = t.xBufOff;
byteCount = t.byteCount;
}
public void update(byte in) {
xBuf[xBufOff++] = in;
if (xBufOff == xBuf.length) {
processWord(xBuf, 0);
xBufOff = 0;
}
byteCount++;
}
public void update(byte[] in, int inOff, int len) {
//
// fill the current word
//
while ((xBufOff != 0) && (len > 0)) {
update(in[inOff]);
inOff++;
len--;
}
//
// process whole words.
//
while (len > xBuf.length) {
processWord(in, inOff);
inOff += xBuf.length;
len -= xBuf.length;
byteCount += xBuf.length;
}
//
// load in the remainder.
//
while (len > 0) {
update(in[inOff]);
inOff++;
len--;
}
}
public void finish() {
long bitLength = (byteCount << 3);
//
// add the pad bytes.
//
update((byte) 128);
while (xBufOff != 0) {
update((byte) 0);
}
processLength(bitLength);
processBlock();
}
public void reset() {
byteCount = 0;
xBufOff = 0;
for (int i = 0; i < xBuf.length; i++) {
xBuf[i] = 0;
}
}
protected abstract void processWord(byte[] in, int inOff);
protected abstract void processLength(long bitLength);
protected abstract void processBlock();
}

View File

@ -0,0 +1,32 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth;
/**
* 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 HashUtils {
public static final byte[] EAGLER_SHA256_SALT_BASE = new byte[] { (byte) 117, (byte) 43, (byte) 1, (byte) 112,
(byte) 75, (byte) 3, (byte) 188, (byte) 61, (byte) 121, (byte) 31, (byte) 34, (byte) 181, (byte) 234,
(byte) 31, (byte) 247, (byte) 72, (byte) 12, (byte) 168, (byte) 138, (byte) 45, (byte) 143, (byte) 77,
(byte) 118, (byte) 245, (byte) 187, (byte) 242, (byte) 188, (byte) 219, (byte) 160, (byte) 235, (byte) 235,
(byte) 68 };
public static final byte[] EAGLER_SHA256_SALT_SAVE = new byte[] { (byte) 49, (byte) 25, (byte) 39, (byte) 38,
(byte) 253, (byte) 85, (byte) 70, (byte) 245, (byte) 71, (byte) 150, (byte) 253, (byte) 206, (byte) 4,
(byte) 26, (byte) 198, (byte) 249, (byte) 145, (byte) 251, (byte) 232, (byte) 174, (byte) 186, (byte) 98,
(byte) 27, (byte) 232, (byte) 55, (byte) 144, (byte) 83, (byte) 21, (byte) 36, (byte) 55, (byte) 170,
(byte) 118 };
}

View File

@ -0,0 +1,247 @@
/*
* Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth;
/**
* implementation of SHA-1 as outlined in "Handbook of Applied Cryptography",
* pages 346 - 349.
*
* It is interesting to ponder why the, apart from the extra IV, the other
* difference here from MD5 is the "endienness" of the word processing!
*/
public class SHA1Digest extends GeneralDigest {
private static final int DIGEST_LENGTH = 20;
private int H1, H2, H3, H4, H5;
private int[] X = new int[80];
private int xOff;
/**
* Standard constructor
*/
public SHA1Digest() {
reset();
}
/**
* Copy constructor. This will copy the state of the provided message digest.
*/
public SHA1Digest(SHA1Digest t) {
super(t);
H1 = t.H1;
H2 = t.H2;
H3 = t.H3;
H4 = t.H4;
H5 = t.H5;
System.arraycopy(t.X, 0, X, 0, t.X.length);
xOff = t.xOff;
}
public String getAlgorithmName() {
return "SHA-1";
}
public int getDigestSize() {
return DIGEST_LENGTH;
}
protected void processWord(byte[] in, int inOff) {
X[xOff++] = ((in[inOff] & 0xff) << 24) | ((in[inOff + 1] & 0xff) << 16) | ((in[inOff + 2] & 0xff) << 8)
| ((in[inOff + 3] & 0xff));
if (xOff == 16) {
processBlock();
}
}
private void unpackWord(int word, byte[] out, int outOff) {
out[outOff] = (byte) (word >>> 24);
out[outOff + 1] = (byte) (word >>> 16);
out[outOff + 2] = (byte) (word >>> 8);
out[outOff + 3] = (byte) word;
}
protected void processLength(long bitLength) {
if (xOff > 14) {
processBlock();
}
X[14] = (int) (bitLength >>> 32);
X[15] = (int) (bitLength & 0xffffffff);
}
public int doFinal(byte[] out, int outOff) {
finish();
unpackWord(H1, out, outOff);
unpackWord(H2, out, outOff + 4);
unpackWord(H3, out, outOff + 8);
unpackWord(H4, out, outOff + 12);
unpackWord(H5, out, outOff + 16);
reset();
return DIGEST_LENGTH;
}
/**
* reset the chaining variables
*/
public void reset() {
super.reset();
H1 = 0x67452301;
H2 = 0xefcdab89;
H3 = 0x98badcfe;
H4 = 0x10325476;
H5 = 0xc3d2e1f0;
xOff = 0;
for (int i = 0; i != X.length; i++) {
X[i] = 0;
}
}
//
// Additive constants
//
private static final int Y1 = 0x5a827999;
private static final int Y2 = 0x6ed9eba1;
private static final int Y3 = 0x8f1bbcdc;
private static final int Y4 = 0xca62c1d6;
private int f(int u, int v, int w) {
return ((u & v) | ((~u) & w));
}
private int h(int u, int v, int w) {
return (u ^ v ^ w);
}
private int g(int u, int v, int w) {
return ((u & v) | (u & w) | (v & w));
}
private int rotateLeft(int x, int n) {
return (x << n) | (x >>> (32 - n));
}
protected void processBlock() {
//
// expand 16 word block into 80 word block.
//
for (int i = 16; i <= 79; i++) {
X[i] = rotateLeft((X[i - 3] ^ X[i - 8] ^ X[i - 14] ^ X[i - 16]), 1);
}
//
// set up working variables.
//
int A = H1;
int B = H2;
int C = H3;
int D = H4;
int E = H5;
//
// round 1
//
for (int j = 0; j <= 19; j++) {
int t = rotateLeft(A, 5) + f(B, C, D) + E + X[j] + Y1;
E = D;
D = C;
C = rotateLeft(B, 30);
B = A;
A = t;
}
//
// round 2
//
for (int j = 20; j <= 39; j++) {
int t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + Y2;
E = D;
D = C;
C = rotateLeft(B, 30);
B = A;
A = t;
}
//
// round 3
//
for (int j = 40; j <= 59; j++) {
int t = rotateLeft(A, 5) + g(B, C, D) + E + X[j] + Y3;
E = D;
D = C;
C = rotateLeft(B, 30);
B = A;
A = t;
}
//
// round 4
//
for (int j = 60; j <= 79; j++) {
int t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + Y4;
E = D;
D = C;
C = rotateLeft(B, 30);
B = A;
A = t;
}
H1 += A;
H2 += B;
H3 += C;
H4 += D;
H5 += E;
//
// reset the offset and clean out the word buffer.
//
xOff = 0;
for (int i = 0; i != X.length; i++) {
X[i] = 0;
}
}
private static final String hex = "0123456789abcdef";
public static String hash2string(byte[] b) {
char[] ret = new char[b.length * 2];
for(int i = 0; i < b.length; ++i) {
int bb = (int)b[i] & 0xFF;
ret[i * 2] = hex.charAt((bb >> 4) & 0xF);
ret[i * 2 + 1] = hex.charAt(bb & 0xF);
}
return new String(ret);
}
}

View File

@ -0,0 +1,254 @@
/*
* Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth;
public class SHA256Digest extends GeneralDigest {
private static final int DIGEST_LENGTH = 32;
private int H1, H2, H3, H4, H5, H6, H7, H8;
private int[] X = new int[64];
private int xOff;
public SHA256Digest() {
reset();
}
public static int bigEndianToInt(byte[] bs, int off) {
int n = bs[off] << 24;
n |= (bs[++off] & 0xff) << 16;
n |= (bs[++off] & 0xff) << 8;
n |= (bs[++off] & 0xff);
return n;
}
public static void bigEndianToInt(byte[] bs, int off, int[] ns) {
for (int i = 0; i < ns.length; ++i) {
ns[i] = bigEndianToInt(bs, off);
off += 4;
}
}
public static byte[] intToBigEndian(int n) {
byte[] bs = new byte[4];
intToBigEndian(n, bs, 0);
return bs;
}
public static void intToBigEndian(int n, byte[] bs, int off) {
bs[off] = (byte) (n >>> 24);
bs[++off] = (byte) (n >>> 16);
bs[++off] = (byte) (n >>> 8);
bs[++off] = (byte) (n);
}
protected void processWord(byte[] in, int inOff) {
X[xOff] = bigEndianToInt(in, inOff);
if (++xOff == 16) {
processBlock();
}
}
protected void processLength(long bitLength) {
if (xOff > 14) {
processBlock();
}
X[14] = (int) (bitLength >>> 32);
X[15] = (int) (bitLength & 0xffffffff);
}
public int doFinal(byte[] out, int outOff) {
finish();
intToBigEndian(H1, out, outOff);
intToBigEndian(H2, out, outOff + 4);
intToBigEndian(H3, out, outOff + 8);
intToBigEndian(H4, out, outOff + 12);
intToBigEndian(H5, out, outOff + 16);
intToBigEndian(H6, out, outOff + 20);
intToBigEndian(H7, out, outOff + 24);
intToBigEndian(H8, out, outOff + 28);
reset();
return DIGEST_LENGTH;
}
/**
* reset the chaining variables
*/
public void reset() {
super.reset();
/*
* SHA-256 initial hash value The first 32 bits of the fractional parts of the
* square roots of the first eight prime numbers
*/
H1 = 0x6a09e667;
H2 = 0xbb67ae85;
H3 = 0x3c6ef372;
H4 = 0xa54ff53a;
H5 = 0x510e527f;
H6 = 0x9b05688c;
H7 = 0x1f83d9ab;
H8 = 0x5be0cd19;
xOff = 0;
for (int i = 0; i != X.length; i++) {
X[i] = 0;
}
}
protected void processBlock() {
//
// expand 16 word block into 64 word blocks.
//
for (int t = 16; t <= 63; t++) {
X[t] = Theta1(X[t - 2]) + X[t - 7] + Theta0(X[t - 15]) + X[t - 16];
}
//
// set up working variables.
//
int a = H1;
int b = H2;
int c = H3;
int d = H4;
int e = H5;
int f = H6;
int g = H7;
int h = H8;
int t = 0;
for (int i = 0; i < 8; i++) {
// t = 8 * i
h += Sum1(e) + Ch(e, f, g) + K[t] + X[t];
d += h;
h += Sum0(a) + Maj(a, b, c);
++t;
// t = 8 * i + 1
g += Sum1(d) + Ch(d, e, f) + K[t] + X[t];
c += g;
g += Sum0(h) + Maj(h, a, b);
++t;
// t = 8 * i + 2
f += Sum1(c) + Ch(c, d, e) + K[t] + X[t];
b += f;
f += Sum0(g) + Maj(g, h, a);
++t;
// t = 8 * i + 3
e += Sum1(b) + Ch(b, c, d) + K[t] + X[t];
a += e;
e += Sum0(f) + Maj(f, g, h);
++t;
// t = 8 * i + 4
d += Sum1(a) + Ch(a, b, c) + K[t] + X[t];
h += d;
d += Sum0(e) + Maj(e, f, g);
++t;
// t = 8 * i + 5
c += Sum1(h) + Ch(h, a, b) + K[t] + X[t];
g += c;
c += Sum0(d) + Maj(d, e, f);
++t;
// t = 8 * i + 6
b += Sum1(g) + Ch(g, h, a) + K[t] + X[t];
f += b;
b += Sum0(c) + Maj(c, d, e);
++t;
// t = 8 * i + 7
a += Sum1(f) + Ch(f, g, h) + K[t] + X[t];
e += a;
a += Sum0(b) + Maj(b, c, d);
++t;
}
H1 += a;
H2 += b;
H3 += c;
H4 += d;
H5 += e;
H6 += f;
H7 += g;
H8 += h;
//
// reset the offset and clean out the word buffer.
//
xOff = 0;
for (int i = 0; i < 16; i++) {
X[i] = 0;
}
}
/* SHA-256 functions */
private static int Ch(int x, int y, int z) {
return (x & y) ^ ((~x) & z);
// return z ^ (x & (y ^ z));
}
private static int Maj(int x, int y, int z) {
// return (x & y) ^ (x & z) ^ (y & z);
return (x & y) | (z & (x ^ y));
}
private static int Sum0(int x) {
return ((x >>> 2) | (x << 30)) ^ ((x >>> 13) | (x << 19)) ^ ((x >>> 22) | (x << 10));
}
private static int Sum1(int x) {
return ((x >>> 6) | (x << 26)) ^ ((x >>> 11) | (x << 21)) ^ ((x >>> 25) | (x << 7));
}
private static int Theta0(int x) {
return ((x >>> 7) | (x << 25)) ^ ((x >>> 18) | (x << 14)) ^ (x >>> 3);
}
private static int Theta1(int x) {
return ((x >>> 17) | (x << 15)) ^ ((x >>> 19) | (x << 13)) ^ (x >>> 10);
}
/*
* SHA-256 Constants (represent the first 32 bits of the fractional parts of the
* cube roots of the first sixty-four prime numbers)
*/
static final int K[] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152,
0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138,
0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70,
0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa,
0xa4506ceb, 0xbef9a3f7, 0xc67178f2 };
}

View File

@ -0,0 +1,50 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.SHA1Digest;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Command;
/**
* 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 CommandConfirmCode extends Command {
public static String confirmHash = null;
public CommandConfirmCode() {
super("confirm-code", "eaglercraft.command.confirmcode", "confirmcode");
}
@Override
public void execute(CommandSender var1, String[] var2) {
if(var2.length != 1) {
var1.sendMessage(new TextComponent(ChatColor.RED + "How to use: " + ChatColor.WHITE + "/confirm-code <code>"));
}else {
var1.sendMessage(new TextComponent(ChatColor.YELLOW + "Server list 2FA code has been set to: " + ChatColor.GREEN + var2[0]));
var1.sendMessage(new TextComponent(ChatColor.YELLOW + "You can now return to the server list site and continue"));
byte[] bts = var2[0].getBytes(StandardCharsets.US_ASCII);
SHA1Digest dg = new SHA1Digest();
dg.update(bts, 0, bts.length);
byte[] f = new byte[20];
dg.doFinal(f, 0);
confirmHash = SHA1Digest.hash2string(f);
}
}
}

View File

@ -0,0 +1,57 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
/**
* 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 CommandDomain extends Command {
public CommandDomain() {
super("domain", "eaglercraft.command.domain");
}
@Override
public void execute(CommandSender var1, String[] var2) {
if(var2.length != 1) {
var1.sendMessage(new TextComponent(ChatColor.RED + "How to use: " + ChatColor.WHITE + "/domain <player>"));
}else {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(var2[0]);
if(player == null) {
var1.sendMessage(new TextComponent(ChatColor.RED + "That user is not online"));
return;
}
PendingConnection conn = player.getPendingConnection();
if(!(conn instanceof EaglerInitialHandler)) {
var1.sendMessage(new TextComponent(ChatColor.RED + "That user is not using Eaglercraft"));
return;
}
String origin = ((EaglerInitialHandler)conn).origin;
if(origin != null) {
var1.sendMessage(new TextComponent(ChatColor.BLUE + "Domain of " + var2[0] + " is '" + origin + "'"));
}else {
var1.sendMessage(new TextComponent(ChatColor.RED + "That user's browser did not send an origin header"));
}
}
}
}

View File

@ -0,0 +1,82 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command;
import java.util.logging.Level;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.DefaultAuthSystem;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.DefaultAuthSystem.AuthException;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerAuthConfig;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.command.ConsoleCommandSender;
/**
* 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 CommandEaglerPurge extends Command {
public CommandEaglerPurge(String name) {
super(name + "-purge", "eaglercraft.command.purge");
}
@Override
public void execute(CommandSender var1, String[] var2) {
if(var1 instanceof ConsoleCommandSender) {
if(var2.length != 1) {
TextComponent comp = new TextComponent("Use /" + getName() + " <maxAge>");
comp.setColor(ChatColor.RED);
var1.sendMessage(comp);
return;
}
int mx;
try {
mx = Integer.parseInt(var2[0]);
}catch(NumberFormatException ex) {
TextComponent comp = new TextComponent("'" + var2[0] + "' is not an integer!");
comp.setColor(ChatColor.RED);
var1.sendMessage(comp);
return;
}
EaglerAuthConfig authConf = EaglerXBungee.getEagler().getConfig().getAuthConfig();
if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) {
DefaultAuthSystem srv = EaglerXBungee.getEagler().getAuthService();
if(srv != null) {
int cnt;
try {
EaglerXBungee.logger().warning("Console is attempting to purge all accounts with " + mx + " days of inactivity");
cnt = srv.pruneUsers(System.currentTimeMillis() - mx * 86400000l);
}catch(AuthException ex) {
EaglerXBungee.logger().log(Level.SEVERE, "Failed to purge accounts", ex);
TextComponent comp = new TextComponent("Failed to purge, check log! Reason: " + ex.getMessage());
comp.setColor(ChatColor.AQUA);
var1.sendMessage(comp);
return;
}
EaglerXBungee.logger().warning("Console purged " + cnt + " accounts from auth database");
TextComponent comp = new TextComponent("Purged " + cnt + " old accounts from the database");
comp.setColor(ChatColor.AQUA);
var1.sendMessage(comp);
}
}
}else {
TextComponent comp = new TextComponent("This command can only be run from the console!");
comp.setColor(ChatColor.RED);
var1.sendMessage(comp);
}
}
}

View File

@ -0,0 +1,75 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command;
import java.util.logging.Level;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.DefaultAuthSystem;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerAuthConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
/**
* 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 CommandEaglerRegister extends Command {
public CommandEaglerRegister(String name) {
super(name);
}
@Override
public void execute(CommandSender sender, String[] args) {
if(sender instanceof ProxiedPlayer) {
ProxiedPlayer player = (ProxiedPlayer)sender;
if(args.length != 1) {
TextComponent comp = new TextComponent("Use: /" + getName() + " <password>");
comp.setColor(ChatColor.RED);
player.sendMessage(comp);
return;
}
EaglerAuthConfig authConf = EaglerXBungee.getEagler().getConfig().getAuthConfig();
if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) {
DefaultAuthSystem srv = EaglerXBungee.getEagler().getAuthService();
if(srv != null) {
if(!(player.getPendingConnection() instanceof EaglerInitialHandler)) {
try {
srv.processSetPassword(player, args[0]);
sender.sendMessage(new TextComponent(authConf.getCommandSuccessText()));
}catch(DefaultAuthSystem.TooManyRegisteredOnIPException ex) {
String tooManyReg = authConf.getTooManyRegistrationsMessage();
sender.sendMessage(new TextComponent(tooManyReg));
}catch(DefaultAuthSystem.AuthException ex) {
EaglerXBungee.logger().log(Level.SEVERE, "Internal exception while processing password change from \"" + player.getName() + "\"", ex);
TextComponent comp = new TextComponent("Internal error, check console logs");
comp.setColor(ChatColor.RED);
sender.sendMessage(comp);
}
}else {
player.sendMessage(new TextComponent(authConf.getNeedVanillaToRegisterMessage()));
}
}
}
}else {
TextComponent comp = new TextComponent("You must be a player to use this command!");
comp.setColor(ChatColor.RED);
sender.sendMessage(comp);
}
}
}

View File

@ -0,0 +1,88 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.command;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerRateLimiter;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Command;
/**
* 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 CommandRatelimit extends Command {
public CommandRatelimit() {
super("ratelimit", "eaglercraft.command.ratelimit");
}
@Override
public void execute(CommandSender sender, String[] args) {
if((args.length != 1 && args.length != 2) || !args[0].equalsIgnoreCase("reset")) {
TextComponent comp = new TextComponent("Usage: /ratelimit reset [ip|login|motd|query]"); //TODO: allow reset ratelimit on specific listeners
comp.setColor(ChatColor.RED);
sender.sendMessage(comp);
}else {
int resetNum = 0;
if(args.length == 2) {
if(args[1].equalsIgnoreCase("ip")) {
resetNum = 1;
}else if(args[1].equalsIgnoreCase("login")) {
resetNum = 2;
}else if(args[1].equalsIgnoreCase("motd")) {
resetNum = 3;
}else if(args[1].equalsIgnoreCase("query")) {
resetNum = 4;
}else {
TextComponent comp = new TextComponent("Unknown ratelimit '" + args[1] + "'!");
comp.setColor(ChatColor.RED);
sender.sendMessage(comp);
return;
}
}
EaglerBungeeConfig conf = EaglerXBungee.getEagler().getConfig();
for(EaglerListenerConfig listener : conf.getServerListeners()) {
if(resetNum == 0 || resetNum == 1) {
EaglerRateLimiter limiter = listener.getRatelimitIp();
if(limiter != null) {
limiter.reset();
}
}
if(resetNum == 0 || resetNum == 2) {
EaglerRateLimiter limiter = listener.getRatelimitLogin();
if(limiter != null) {
limiter.reset();
}
}
if(resetNum == 0 || resetNum == 3) {
EaglerRateLimiter limiter = listener.getRatelimitMOTD();
if(limiter != null) {
limiter.reset();
}
}
if(resetNum == 0 || resetNum == 4) {
EaglerRateLimiter limiter = listener.getRatelimitQuery();
if(limiter != null) {
limiter.reset();
}
}
}
sender.sendMessage(new TextComponent("Ratelimits reset."));
}
}
}

View File

@ -0,0 +1,165 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.config.Configuration;
/**
* 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 EaglerAuthConfig {
static EaglerAuthConfig loadConfig(Configuration config) {
boolean enableAuthentication = config.getBoolean("enable_authentication_system");
boolean useBuiltInAuthentication = config.getBoolean("use_onboard_eaglerx_system");
String databaseURI = config.getString("auth_db_uri");
String driverClass = config.getString("sql_driver_class", "internal");
String driverPath = config.getString("sql_driver_path", null);
String passwordPromptScreenText = ChatColor.translateAlternateColorCodes('&', config.getString("password_prompt_screen_text", ""));
String notRegisteredScreenText = ChatColor.translateAlternateColorCodes('&', config.getString("not_registered_screen_text", ""));
String wrongPasswordScreenText = ChatColor.translateAlternateColorCodes('&', config.getString("wrong_password_screen_text", ""));
String eaglerCommandName = config.getString("eagler_command_name");
String useRegisterCommandText = ChatColor.translateAlternateColorCodes('&', config.getString("use_register_command_text", ""));
String useChangeCommandText = ChatColor.translateAlternateColorCodes('&', config.getString("use_change_command_text", ""));
String commandSuccessText = ChatColor.translateAlternateColorCodes('&', config.getString("command_success_text", ""));
String lastEaglerLoginMessage = ChatColor.translateAlternateColorCodes('&', config.getString("last_eagler_login_message", ""));
String tooManyRegistrationsMessage = ChatColor.translateAlternateColorCodes('&', config.getString("too_many_registrations_message", ""));
String needVanillaToRegisterMessage = ChatColor.translateAlternateColorCodes('&', config.getString("need_vanilla_to_register_message", ""));
boolean overrideEaglerToVanillaSkins = config.getBoolean("override_eagler_to_vanilla_skins");
int maxRegistrationsPerIP = config.getInt("max_registration_per_ip", -1);
return new EaglerAuthConfig(enableAuthentication, useBuiltInAuthentication, databaseURI, driverClass,
driverPath, passwordPromptScreenText, wrongPasswordScreenText, notRegisteredScreenText,
eaglerCommandName, useRegisterCommandText, useChangeCommandText, commandSuccessText,
lastEaglerLoginMessage, tooManyRegistrationsMessage, needVanillaToRegisterMessage,
overrideEaglerToVanillaSkins, maxRegistrationsPerIP);
}
private boolean enableAuthentication;
private boolean useBuiltInAuthentication;
private final String databaseURI;
private final String driverClass;
private final String driverPath;
private final String passwordPromptScreenText;
private final String wrongPasswordScreenText;
private final String notRegisteredScreenText;
private final String eaglerCommandName;
private final String useRegisterCommandText;
private final String useChangeCommandText;
private final String commandSuccessText;
private final String lastEaglerLoginMessage;
private final String tooManyRegistrationsMessage;
private final String needVanillaToRegisterMessage;
private final boolean overrideEaglerToVanillaSkins;
private final int maxRegistrationsPerIP;
private EaglerAuthConfig(boolean enableAuthentication, boolean useBuiltInAuthentication, String databaseURI,
String driverClass, String driverPath, String passwordPromptScreenText, String wrongPasswordScreenText,
String notRegisteredScreenText, String eaglerCommandName, String useRegisterCommandText,
String useChangeCommandText, String commandSuccessText, String lastEaglerLoginMessage,
String tooManyRegistrationsMessage, String needVanillaToRegisterMessage,
boolean overrideEaglerToVanillaSkins, int maxRegistrationsPerIP) {
this.enableAuthentication = enableAuthentication;
this.useBuiltInAuthentication = useBuiltInAuthentication;
this.databaseURI = databaseURI;
this.driverClass = driverClass;
this.driverPath = driverPath;
this.passwordPromptScreenText = passwordPromptScreenText;
this.wrongPasswordScreenText = wrongPasswordScreenText;
this.notRegisteredScreenText = notRegisteredScreenText;
this.eaglerCommandName = eaglerCommandName;
this.useRegisterCommandText = useRegisterCommandText;
this.useChangeCommandText = useChangeCommandText;
this.commandSuccessText = commandSuccessText;
this.lastEaglerLoginMessage = lastEaglerLoginMessage;
this.tooManyRegistrationsMessage = tooManyRegistrationsMessage;
this.needVanillaToRegisterMessage = needVanillaToRegisterMessage;
this.overrideEaglerToVanillaSkins = overrideEaglerToVanillaSkins;
this.maxRegistrationsPerIP = maxRegistrationsPerIP;
}
public boolean isEnableAuthentication() {
return enableAuthentication;
}
public boolean isUseBuiltInAuthentication() {
return useBuiltInAuthentication;
}
public void triggerOnlineModeDisabled() {
enableAuthentication = false;
useBuiltInAuthentication = false;
}
public String getDatabaseURI() {
return databaseURI;
}
public String getDriverClass() {
return driverClass;
}
public String getDriverPath() {
return driverPath;
}
public String getPasswordPromptScreenText() {
return passwordPromptScreenText;
}
public String getWrongPasswordScreenText() {
return wrongPasswordScreenText;
}
public String getNotRegisteredScreenText() {
return notRegisteredScreenText;
}
public String getEaglerCommandName() {
return eaglerCommandName;
}
public String getUseRegisterCommandText() {
return useRegisterCommandText;
}
public String getUseChangeCommandText() {
return useChangeCommandText;
}
public String getCommandSuccessText() {
return commandSuccessText;
}
public String getLastEaglerLoginMessage() {
return lastEaglerLoginMessage;
}
public String getTooManyRegistrationsMessage() {
return tooManyRegistrationsMessage;
}
public String getNeedVanillaToRegisterMessage() {
return needVanillaToRegisterMessage;
}
public boolean getOverrideEaglerToVanillaSkins() {
return overrideEaglerToVanillaSkins;
}
public int getMaxRegistrationsPerIP() {
return maxRegistrationsPerIP;
}
}

View File

@ -0,0 +1,478 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpContentType;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.md_5.bungee.protocol.Property;
/**
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglerBungeeConfig {
public static EaglerBungeeConfig loadConfig(File directory) throws IOException {
Map<String, HttpContentType> contentTypes = new HashMap();
try(InputStream is = new FileInputStream(getConfigFile(directory, "http_mime_types.json"))) {
loadMimeTypes(is, contentTypes);
}catch(Throwable t) {
try(InputStream is = EaglerBungeeConfig.class.getResourceAsStream("default_http_mime_types.json")) {
loadMimeTypes(is, contentTypes);
}catch(IOException ex) {
EaglerXBungee.logger().severe("Could not load default_http_mime_types.json!");
throw new RuntimeException(ex);
}
}
directory.mkdirs();
ConfigurationProvider prov = ConfigurationProvider.getProvider(YamlConfiguration.class);
Configuration configYml = prov.load(getConfigFile(directory, "settings.yml"));
String serverName = configYml.getString("server_name", "EaglercraftXBungee Server");
String serverUUIDString = configYml.getString("server_uuid", null);
if(serverUUIDString == null) {
throw new IOException("You must specify a server_uuid!");
}
UUID serverUUID = null;
try {
serverUUID = UUID.fromString(serverUUIDString);
}catch(Throwable t) {
}
if(serverUUID == null) {
throw new IOException("The server_uuid \"" + serverUUIDString + "\" is invalid!");
}
Configuration listenerYml = prov.load(getConfigFile(directory, "listeners.yml"));
Iterator<String> listeners = listenerYml.getKeys().iterator();
Map<String, EaglerListenerConfig> serverListeners = new HashMap();
boolean voiceChat = false;
while(listeners.hasNext()) {
String name = listeners.next();
EaglerListenerConfig conf = EaglerListenerConfig.loadConfig(listenerYml.getSection(name), contentTypes);
if(conf != null) {
serverListeners.put(name, conf);
voiceChat |= conf.getEnableVoiceChat();
}else {
EaglerXBungee.logger().severe("Invalid listener config: " + name);
}
}
if(serverListeners.size() == 0) {
EaglerXBungee.logger().severe("No Listeners Configured!");
}
Configuration authserivceYml = prov.load(getConfigFile(directory, "authservice.yml"));
EaglerAuthConfig authConfig = EaglerAuthConfig.loadConfig(authserivceYml);
Configuration updatesYml = prov.load(getConfigFile(directory, "updates.yml"));
EaglerUpdateConfig updatesConfig = EaglerUpdateConfig.loadConfig(updatesYml);
Configuration iceServersYml = prov.load(getConfigFile(directory, "ice_servers.yml"));
Collection<String> iceServers = loadICEServers(iceServersYml);
if(authConfig.isEnableAuthentication()) {
for(EaglerListenerConfig lst : serverListeners.values()) {
if(lst.getRatelimitLogin() != null) lst.getRatelimitLogin().setDivisor(2);
if(lst.getRatelimitIp() != null) lst.getRatelimitIp().setDivisor(2);
}
}
long websocketKeepAliveTimeout = configYml.getInt("websocket_connection_timeout", 15000);
long websocketHandshakeTimeout = configYml.getInt("websocket_handshake_timeout", 5000);
int websocketCompressionLevel = configYml.getInt("http_websocket_compression_level", 6);
boolean downloadVanillaSkins = configYml.getBoolean("download_vanilla_skins_to_clients", false);
Collection<String> validSkinUrls = (Collection<String>)configYml.getList("valid_skin_download_urls");
int uuidRateLimitPlayer = configYml.getInt("uuid_lookup_ratelimit_player", 50);
int uuidRateLimitGlobal = configYml.getInt("uuid_lookup_ratelimit_global", 175);
int skinRateLimitPlayer = configYml.getInt("skin_download_ratelimit_player", 1000);
int skinRateLimitGlobal = configYml.getInt("skin_download_ratelimit_global", 30000);
String skinCacheURI = configYml.getString("skin_cache_db_uri", "jdbc:sqlite:eaglercraft_skins_cache.db");
int keepObjectsDays = configYml.getInt("skin_cache_keep_objects_days", 45);
int keepProfilesDays = configYml.getInt("skin_cache_keep_profiles_days", 7);
int maxObjects = configYml.getInt("skin_cache_max_objects", 32768);
int maxProfiles = configYml.getInt("skin_cache_max_profiles", 32768);
int antagonistsRateLimit = configYml.getInt("skin_cache_antagonists_ratelimit", 15);
String sqliteDriverClass = configYml.getString("sql_driver_class", "internal");
String sqliteDriverPath = configYml.getString("sql_driver_path", null);
String eaglerPlayersVanillaSkin = configYml.getString("eagler_players_vanilla_skin", null);
if(eaglerPlayersVanillaSkin != null && eaglerPlayersVanillaSkin.length() == 0) {
eaglerPlayersVanillaSkin = null;
}
boolean enableIsEaglerPlayerProperty = configYml.getBoolean("enable_is_eagler_player_property", true);
Set<String> disableVoiceOnServers = new HashSet((Collection<String>)configYml.getList("disable_voice_chat_on_servers"));
boolean disableFNAWSkinsEverywhere = configYml.getBoolean("disable_fnaw_skins_everywhere", false);
Set<String> disableFNAWSkinsOnServers = new HashSet((Collection<String>)configYml.getList("disable_fnaw_skins_on_servers"));
final EaglerBungeeConfig ret = new EaglerBungeeConfig(serverName, serverUUID, websocketKeepAliveTimeout,
websocketHandshakeTimeout, websocketCompressionLevel, serverListeners, contentTypes,
downloadVanillaSkins, validSkinUrls, uuidRateLimitPlayer, uuidRateLimitGlobal, skinRateLimitPlayer,
skinRateLimitGlobal, skinCacheURI, keepObjectsDays, keepProfilesDays, maxObjects, maxProfiles,
antagonistsRateLimit, sqliteDriverClass, sqliteDriverPath, eaglerPlayersVanillaSkin,
enableIsEaglerPlayerProperty, authConfig, updatesConfig, iceServers, voiceChat,
disableVoiceOnServers, disableFNAWSkinsEverywhere, disableFNAWSkinsOnServers);
if(eaglerPlayersVanillaSkin != null) {
VanillaDefaultSkinProfileLoader.lookupVanillaSkinUser(ret);
}
return ret;
}
private static File getConfigFile(File directory, String fileName) throws IOException {
File file = new File(directory, fileName);
if(!file.isFile()) {
try (BufferedReader is = new BufferedReader(new InputStreamReader(
EaglerBungeeConfig.class.getResourceAsStream("default_" + fileName), StandardCharsets.UTF_8));
PrintWriter os = new PrintWriter(new FileWriter(file))) {
String line;
while((line = is.readLine()) != null) {
if(line.contains("${")) {
line = line.replace("${random_uuid}", UUID.randomUUID().toString());
}
os.println(line);
}
}
}
return file;
}
private static void loadMimeTypes(InputStream file, Map<String, HttpContentType> contentTypes) throws IOException {
JsonObject obj = parseJsonObject(file);
for(Entry<String, JsonElement> etr : obj.entrySet()) {
String mime = etr.getKey();
try {
JsonObject e = etr.getValue().getAsJsonObject();
JsonArray arr = e.getAsJsonArray("files");
if(arr == null || arr.size() == 0) {
EaglerXBungee.logger().warning("MIME type '" + mime + "' defines no extensions!");
continue;
}
HashSet<String> exts = new HashSet();
for(int i = 0, l = arr.size(); i < l; ++i) {
exts.add(arr.get(i).getAsString());
}
long expires = 0l;
JsonElement ex = e.get("expires");
if(ex != null) {
expires = ex.getAsInt() * 1000l;
}
String charset = null;
ex = e.get("charset");
if(ex != null) {
charset = ex.getAsString();
}
HttpContentType typeObj = new HttpContentType(exts, mime, charset, expires);
for(String s : exts) {
contentTypes.put(s, typeObj);
}
}catch(Throwable t) {
EaglerXBungee.logger().warning("Exception parsing MIME type '" + mime + "' - " + t.toString());
}
}
}
private static Collection<String> loadICEServers(Configuration config) {
Collection<String> ret = new ArrayList(config.getList("voice_stun_servers"));
Configuration turnServers = config.getSection("voice_turn_servers");
Iterator<String> turnItr = turnServers.getKeys().iterator();
while(turnItr.hasNext()) {
String name = turnItr.next();
Configuration turnSvr = turnServers.getSection(name);
ret.add(turnSvr.getString("url") + ";" + turnSvr.getString("username") + ";" + turnSvr.getString("password"));
}
return ret;
}
@SuppressWarnings("deprecation")
private static JsonObject parseJsonObject(InputStream file) throws IOException {
StringBuilder str = new StringBuilder();
byte[] buffer = new byte[8192];
int i;
while((i = file.read(buffer)) > 0) {
str.append(new String(buffer, 0, i, "UTF-8"));
}
try {
return (new JsonParser()).parse(str.toString()).getAsJsonObject();
}catch(JsonSyntaxException ex) {
throw new IOException("Invalid JSONObject", ex);
}
}
public static final Property isEaglerProperty = new Property("isEaglerPlayer", "true");
private final String serverName;
private final UUID serverUUID;
private final long websocketKeepAliveTimeout;
private final long websocketHandshakeTimeout;
private final int httpWebsocketCompressionLevel;
private final Map<String, EaglerListenerConfig> serverListeners;
private final Map<String, HttpContentType> contentTypes;
private final boolean downloadVanillaSkins;
private final Collection<String> validSkinUrls;
private final int uuidRateLimitPlayer;
private final int uuidRateLimitGlobal;
private final int skinRateLimitPlayer;
private final int skinRateLimitGlobal;
private final String skinCacheURI;
private final int keepObjectsDays;
private final int keepProfilesDays;
private final int maxObjects;
private final int maxProfiles;
private final int antagonistsRateLimit;
private final String sqliteDriverClass;
private final String sqliteDriverPath;
private final String eaglerPlayersVanillaSkin;
private final boolean enableIsEaglerPlayerProperty;
private final EaglerAuthConfig authConfig;
private final EaglerUpdateConfig updateConfig;
private final Collection<String> iceServers;
private final boolean enableVoiceChat;
private final Set<String> disableVoiceOnServers;
private final boolean disableFNAWSkinsEverywhere;
private final Set<String> disableFNAWSkinsOnServers;
Property[] eaglerPlayersVanillaSkinCached = new Property[] { isEaglerProperty };
public String getServerName() {
return serverName;
}
public UUID getServerUUID() {
return serverUUID;
}
public long getWebsocketKeepAliveTimeout() {
return websocketKeepAliveTimeout;
}
public long getWebsocketHandshakeTimeout() {
return websocketHandshakeTimeout;
}
public int getHttpWebsocketCompressionLevel() {
return httpWebsocketCompressionLevel;
}
public Collection<EaglerListenerConfig> getServerListeners() {
return serverListeners.values();
}
public EaglerListenerConfig getServerListenersByName(String name) {
return serverListeners.get(name);
}
public Map<String, HttpContentType> getContentType() {
return contentTypes;
}
public Map<String, HttpContentType> getContentTypes() {
return contentTypes;
}
public boolean getDownloadVanillaSkins() {
return downloadVanillaSkins;
}
public Collection<String> getValidSkinUrls() {
return validSkinUrls;
}
public boolean isValidSkinHost(String host) {
host = host.toLowerCase();
for(String str : validSkinUrls) {
if(str.length() > 0) {
str = str.toLowerCase();
if(str.charAt(0) == '*') {
if(host.endsWith(str.substring(1))) {
return true;
}
}else {
if(host.equals(str)) {
return true;
}
}
}
}
return false;
}
public int getUuidRateLimitPlayer() {
return uuidRateLimitPlayer;
}
public int getUuidRateLimitGlobal() {
return uuidRateLimitGlobal;
}
public int getSkinRateLimitPlayer() {
return skinRateLimitPlayer;
}
public int getSkinRateLimitGlobal() {
return skinRateLimitGlobal;
}
public String getSkinCacheURI() {
return skinCacheURI;
}
public String getSQLiteDriverClass() {
return sqliteDriverClass;
}
public String getSQLiteDriverPath() {
return sqliteDriverPath;
}
public int getKeepObjectsDays() {
return keepObjectsDays;
}
public int getKeepProfilesDays() {
return keepProfilesDays;
}
public int getMaxObjects() {
return maxObjects;
}
public int getMaxProfiles() {
return maxProfiles;
}
public int getAntagonistsRateLimit() {
return antagonistsRateLimit;
}
public String getEaglerPlayersVanillaSkin() {
return eaglerPlayersVanillaSkin;
}
public boolean getEnableIsEaglerPlayerProperty() {
return enableIsEaglerPlayerProperty;
}
public Property[] getEaglerPlayersVanillaSkinProperties() {
return eaglerPlayersVanillaSkinCached;
}
public boolean isCracked() {
return true;
}
public EaglerAuthConfig getAuthConfig() {
return authConfig;
}
public EaglerUpdateConfig getUpdateConfig() {
return updateConfig;
}
public Collection<String> getICEServers() {
return iceServers;
}
public boolean getEnableVoiceChat() {
return enableVoiceChat;
}
public Set<String> getDisableVoiceOnServersSet() {
return disableVoiceOnServers;
}
public boolean getDisableFNAWSkinsEverywhere() {
return disableFNAWSkinsEverywhere;
}
public Set<String> getDisableFNAWSkinsOnServersSet() {
return disableFNAWSkinsOnServers;
}
private EaglerBungeeConfig(String serverName, UUID serverUUID, long websocketKeepAliveTimeout,
long websocketHandshakeTimeout, int httpWebsocketCompressionLevel,
Map<String, EaglerListenerConfig> serverListeners, Map<String, HttpContentType> contentTypes,
boolean downloadVanillaSkins, Collection<String> validSkinUrls, int uuidRateLimitPlayer,
int uuidRateLimitGlobal, int skinRateLimitPlayer, int skinRateLimitGlobal, String skinCacheURI,
int keepObjectsDays, int keepProfilesDays, int maxObjects, int maxProfiles, int antagonistsRateLimit,
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) {
this.serverName = serverName;
this.serverUUID = serverUUID;
this.serverListeners = serverListeners;
this.websocketHandshakeTimeout = websocketHandshakeTimeout;
this.websocketKeepAliveTimeout = websocketKeepAliveTimeout;
this.httpWebsocketCompressionLevel = httpWebsocketCompressionLevel;
this.contentTypes = contentTypes;
this.downloadVanillaSkins = downloadVanillaSkins;
this.validSkinUrls = validSkinUrls;
this.uuidRateLimitPlayer = uuidRateLimitPlayer;
this.uuidRateLimitGlobal = uuidRateLimitGlobal;
this.skinRateLimitPlayer = skinRateLimitPlayer;
this.skinRateLimitGlobal = skinRateLimitGlobal;
this.skinCacheURI = skinCacheURI;
this.keepObjectsDays = keepObjectsDays;
this.keepProfilesDays = keepProfilesDays;
this.maxObjects = maxObjects;
this.maxProfiles = maxProfiles;
this.antagonistsRateLimit = antagonistsRateLimit;
this.sqliteDriverClass = sqliteDriverClass;
this.sqliteDriverPath = sqliteDriverPath;
this.eaglerPlayersVanillaSkin = eaglerPlayersVanillaSkin;
this.enableIsEaglerPlayerProperty = enableIsEaglerPlayerProperty;
this.authConfig = authConfig;
this.updateConfig = updateConfig;
this.iceServers = iceServers;
this.enableVoiceChat = enableVoiceChat;
this.disableVoiceOnServers = disableVoiceOnServers;
this.disableFNAWSkinsEverywhere = disableFNAWSkinsEverywhere;
this.disableFNAWSkinsOnServers = disableFNAWSkinsOnServers;
}
}

View File

@ -0,0 +1,312 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.io.File;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import io.netty.handler.codec.http.HttpRequest;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpContentType;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.config.Configuration;
/**
* 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 EaglerListenerConfig extends ListenerInfo {
static EaglerListenerConfig loadConfig(Configuration config, Map<String, HttpContentType> contentTypes) {
String host = config.getString("address", "0.0.0.0:8081");
InetSocketAddress hostv4 = null;
if(host != null && !host.equalsIgnoreCase("null") && !host.equalsIgnoreCase("none")) {
int i = host.lastIndexOf(':');
if(i == -1) {
throw new IllegalArgumentException("Invalid address: " + host + "! Must be an ipv4:port combo");
}
hostv4 = new InetSocketAddress(host.substring(0, i), Integer.parseInt(host.substring(i + 1)));
}
String hostV6 = config.getString("address_v6", "null");
InetSocketAddress hostv6 = null;
if(hostV6 != null && !hostV6.equalsIgnoreCase("null") && !hostV6.equalsIgnoreCase("none") && hostV6.length() > 0) {
int i = hostV6.lastIndexOf(':');
if(i == -1) {
throw new IllegalArgumentException("Invalid address: " + host + "! Must be an ipv6:port combo");
}
hostv6 = new InetSocketAddress(hostV6.substring(0, i), Integer.parseInt(hostV6.substring(i + 1)));
}
if(hostv4 == null && hostv6 == null) {
throw new IllegalArgumentException("Invalid host specifies no addresses, both v4 and v6 address are null");
}
int maxPlayer = config.getInt("max_players", 60);
String tabListType = config.getString("tab_list", "GLOBAL_PING");
String defaultServer = config.getString("default_server", "lobby");
boolean forceDefaultServer = config.getBoolean("force_default_server", false);
boolean forwardIp = config.getBoolean("forward_ip", false);
String forwardIpHeader = config.getString("forward_ip_header", "X-Real-IP");
String redirectLegacyClientsTo = config.getString("redirect_legacy_clients_to", "null");
if(redirectLegacyClientsTo != null && (redirectLegacyClientsTo.equalsIgnoreCase("null") || redirectLegacyClientsTo.length() == 0)) {
redirectLegacyClientsTo = null;
}
String serverIcon = config.getString("server_icon", "server-icon.png");
List<String> serverMOTD = (List<String>) config.getList("server_motd", Arrays.asList("&6An EaglercraftX server"));
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);
int cacheTTL = 7200;
boolean cacheAnimation = false;
boolean cacheResults = true;
boolean cacheTrending = true;
boolean cachePortfolios = false;
Configuration cacheConf = config.getSection("request_motd_cache");
if(cacheConf != null) {
cacheTTL = cacheConf.getInt("cache_ttl", 7200);
cacheAnimation = cacheConf.getBoolean("online_server_list_animation", false);
cacheResults = cacheConf.getBoolean("online_server_list_results", true);
cacheTrending = cacheConf.getBoolean("online_server_list_trending", true);
cachePortfolios = cacheConf.getBoolean("online_server_list_portfolios", false);
}
HttpWebServer httpServer = null;
Configuration httpServerConf = config.getSection("http_server");
if(httpServerConf != null && httpServerConf.getBoolean("enabled", false)) {
String rootDirectory = httpServerConf.getString("root", "web");
String page404 = httpServerConf.getString("page_404_not_found", "default");
if(page404 != null && (page404.length() == 0 || page404.equalsIgnoreCase("null") || page404.equalsIgnoreCase("default"))) {
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());
for(int i = 0, l = indexPageRaw.size(); i < l; ++i) {
Object o = indexPageRaw.get(i);
if(o instanceof String) {
indexPage.add((String)o);
}
}
if(indexPage.size() == 0) {
indexPage.addAll(defaultIndex);
}
httpServer = new HttpWebServer(new File(EaglerXBungee.getEagler().getDataFolder(), rootDirectory),
contentTypes, indexPage, page404);
}
boolean enableVoiceChat = config.getBoolean("allow_voice", false);
EaglerRateLimiter ratelimitIp = null;
EaglerRateLimiter ratelimitLogin = null;
EaglerRateLimiter ratelimitMOTD = null;
EaglerRateLimiter ratelimitQuery = null;
Configuration rateLimitConfig = config.getSection("ratelimit");
if(rateLimitConfig != null) {
Configuration ratelimitIpConfig = rateLimitConfig.getSection("ip");
if(ratelimitIpConfig != null && ratelimitIpConfig.getBoolean("enable", false)) {
ratelimitIp = EaglerRateLimiter.loadConfig(ratelimitIpConfig);
}
Configuration ratelimitLoginConfig = rateLimitConfig.getSection("login");
if(ratelimitLoginConfig != null && ratelimitLoginConfig.getBoolean("enable", false)) {
ratelimitLogin = EaglerRateLimiter.loadConfig(ratelimitLoginConfig);
}
Configuration ratelimitMOTDConfig = rateLimitConfig.getSection("motd");
if(ratelimitMOTDConfig != null && ratelimitMOTDConfig.getBoolean("enable", false)) {
ratelimitMOTD = EaglerRateLimiter.loadConfig(ratelimitMOTDConfig);
}
Configuration ratelimitQueryConfig = rateLimitConfig.getSection("query");
if(ratelimitQueryConfig != null && ratelimitQueryConfig.getBoolean("enable", false)) {
ratelimitQuery = EaglerRateLimiter.loadConfig(ratelimitQueryConfig);
}
}
MOTDCacheConfiguration cacheConfig = new MOTDCacheConfiguration(cacheTTL, cacheAnimation, cacheResults,
cacheTrending, cachePortfolios);
return new EaglerListenerConfig(hostv4, hostv6, maxPlayer, tabListType, defaultServer, forceDefaultServer,
forwardIp, forwardIpHeader, redirectLegacyClientsTo, serverIcon, serverMOTD, allowMOTD, allowQuery,
cacheConfig, httpServer, enableVoiceChat, ratelimitIp, ratelimitLogin, ratelimitMOTD, ratelimitQuery);
}
private final InetSocketAddress address;
private final InetSocketAddress addressV6;
private final int maxPlayer;
private final String tabListType;
private final String defaultServer;
private final boolean forceDefaultServer;
private final boolean forwardIp;
private final String forwardIpHeader;
private final String redirectLegacyClientsTo;
private final String serverIcon;
private final List<String> serverMOTD;
private final boolean allowMOTD;
private final boolean allowQuery;
private final MOTDCacheConfiguration motdCacheConfig;
private final HttpWebServer webServer;
private boolean serverIconSet = false;
private int[] serverIconPixels = null;
private final boolean enableVoiceChat;
private final EaglerRateLimiter ratelimitIp;
private final EaglerRateLimiter ratelimitLogin;
private final EaglerRateLimiter ratelimitMOTD;
private final EaglerRateLimiter ratelimitQuery;
public EaglerListenerConfig(InetSocketAddress address, InetSocketAddress addressV6, int maxPlayer,
String tabListType, String defaultServer, boolean forceDefaultServer, boolean forwardIp,
String forwardIpHeader, String redirectLegacyClientsTo, String serverIcon, List<String> serverMOTD,
boolean allowMOTD, boolean allowQuery, MOTDCacheConfiguration motdCacheConfig, HttpWebServer webServer,
boolean enableVoiceChat, EaglerRateLimiter ratelimitIp, EaglerRateLimiter ratelimitLogin,
EaglerRateLimiter ratelimitMOTD, EaglerRateLimiter ratelimitQuery) {
super(address, String.join("\n", serverMOTD), maxPlayer, 60, Arrays.asList(defaultServer), forceDefaultServer,
Collections.emptyMap(), tabListType, false, false, 0, false, false);
this.address = address;
this.addressV6 = addressV6;
this.maxPlayer = maxPlayer;
this.tabListType = tabListType;
this.defaultServer = defaultServer;
this.forceDefaultServer = forceDefaultServer;
this.forwardIp = forwardIp;
this.forwardIpHeader = forwardIpHeader;
this.redirectLegacyClientsTo = redirectLegacyClientsTo;
this.serverIcon = serverIcon;
this.serverMOTD = serverMOTD;
this.allowMOTD = allowMOTD;
this.allowQuery = allowQuery;
this.motdCacheConfig = motdCacheConfig;
this.webServer = webServer;
this.enableVoiceChat = enableVoiceChat;
this.ratelimitIp = ratelimitIp;
this.ratelimitLogin = ratelimitLogin;
this.ratelimitMOTD = ratelimitMOTD;
this.ratelimitQuery = ratelimitQuery;
}
public InetSocketAddress getAddress() {
return address;
}
public InetSocketAddress getAddressV6() {
return addressV6;
}
public int getMaxPlayer() {
return maxPlayer;
}
public String getTabListType() {
return tabListType;
}
public String getDefaultServer() {
return defaultServer;
}
public boolean isForceDefaultServer() {
return forceDefaultServer;
}
public boolean isForwardIp() {
return forwardIp;
}
public String getForwardIpHeader() {
return forwardIpHeader;
}
public String getServerIconName() {
return serverIcon;
}
public int[] getServerIconPixels() {
if(!serverIconSet) {
if(serverIcon != null) {
File f = new File(serverIcon);
if(f.isFile()) {
serverIconPixels = ServerIconLoader.createServerIcon(f);
if(serverIconPixels == null) {
EaglerXBungee.logger().warning("Server icon could not be loaded: " + f.getAbsolutePath());
}
}else {
EaglerXBungee.logger().warning("Server icon is not a file: " + f.getAbsolutePath());
}
}
serverIconSet = true;
}
return serverIconPixels;
}
public List<String> getServerMOTD() {
return serverMOTD;
}
public boolean isAllowMOTD() {
return allowMOTD;
}
public boolean isAllowQuery() {
return allowQuery;
}
public HttpWebServer getWebServer() {
return webServer;
}
public MOTDCacheConfiguration getMOTDCacheConfig() {
return motdCacheConfig;
}
public boolean blockRequest(HttpRequest request) {
return false;
}
public String redirectLegacyClientsTo() {
return redirectLegacyClientsTo;
}
public boolean getEnableVoiceChat() {
return enableVoiceChat;
}
public EaglerRateLimiter getRatelimitIp() {
return ratelimitIp;
}
public EaglerRateLimiter getRatelimitLogin() {
return ratelimitLogin;
}
public EaglerRateLimiter getRatelimitMOTD() {
return ratelimitMOTD;
}
public EaglerRateLimiter getRatelimitQuery() {
return ratelimitQuery;
}
}

View File

@ -0,0 +1,195 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.md_5.bungee.config.Configuration;
/**
* 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 EaglerRateLimiter {
private final int period;
private final int limit;
private final int limitLockout;
private int effectiveLimit;
private int effectiveLimitLockout;
private final int lockoutDuration;
private final List<String> exceptions;
private EaglerRateLimiter(int period, int limit, int limitLockout, int lockoutDuration, List<String> exceptions) {
this.period = period * 1000 / limit;
this.limit = this.effectiveLimit = limit;
this.limitLockout = this.effectiveLimitLockout = limitLockout;
this.lockoutDuration = lockoutDuration * 1000;
this.exceptions = exceptions;
}
public void setDivisor(int d) {
this.effectiveLimit = this.limit * d;
this.effectiveLimitLockout = this.limitLockout * d;
}
public int getPeriod() {
return period;
}
public int getLimit() {
return effectiveLimit;
}
public int getLimitLockout() {
return effectiveLimitLockout;
}
public int getLockoutDuration() {
return lockoutDuration;
}
public List<String> getExceptions() {
return exceptions;
}
public boolean isException(String addr) {
for(int i = 0, l = exceptions.size(); i < l; ++i) {
String str = exceptions.get(i);
int ll = str.length() - 1;
if(str.indexOf('*') == 0) {
if(addr.endsWith(str.substring(1))) {
return true;
}
}else if(str.lastIndexOf('*') == ll) {
if(addr.startsWith(str.substring(ll))) {
return true;
}
}else {
if(addr.equals(str)) {
return true;
}
}
}
return false;
}
protected class RateLimiter {
protected int requestCounter = 0;
protected long lockoutTimestamp = 0l;
protected long cooldownTimestamp = 0l;
protected RateLimitStatus rateLimit() {
long millis = System.currentTimeMillis();
tick(millis);
if(lockoutTimestamp != 0l) {
return RateLimitStatus.LOCKED_OUT;
}else {
if(++requestCounter > EaglerRateLimiter.this.effectiveLimitLockout) {
lockoutTimestamp = millis;
requestCounter = 0;
return RateLimitStatus.LIMITED_NOW_LOCKED_OUT;
}else if(requestCounter > EaglerRateLimiter.this.effectiveLimit) {
return RateLimitStatus.LIMITED;
}else {
return RateLimitStatus.OK;
}
}
}
protected void tick(long millis) {
if(lockoutTimestamp != 0l) {
if(millis - lockoutTimestamp > EaglerRateLimiter.this.lockoutDuration) {
requestCounter = 0;
lockoutTimestamp = 0l;
cooldownTimestamp = millis;
}
}else {
long delta = millis - cooldownTimestamp;
long decr = delta / EaglerRateLimiter.this.period;
if(decr >= requestCounter) {
requestCounter = 0;
cooldownTimestamp = millis;
}else {
requestCounter -= decr;
cooldownTimestamp += decr * EaglerRateLimiter.this.period;
if(requestCounter < 0) {
requestCounter = 0;
}
}
}
}
}
private final Map<String, RateLimiter> ratelimiters = new HashMap();
public RateLimitStatus rateLimit(String addr) {
addr = addr.toLowerCase();
if(isException(addr)) {
return RateLimitStatus.OK;
}else {
RateLimiter limiter;
synchronized(ratelimiters) {
limiter = ratelimiters.get(addr);
if(limiter == null) {
limiter = new RateLimiter();
ratelimiters.put(addr, limiter);
}
}
return limiter.rateLimit();
}
}
public void tick() {
long millis = System.currentTimeMillis();
synchronized(ratelimiters) {
Iterator<RateLimiter> itr = ratelimiters.values().iterator();
while(itr.hasNext()) {
RateLimiter i = itr.next();
i.tick(millis);
if(i.requestCounter <= 0 && i.lockoutTimestamp <= 0l) {
itr.remove();
}
}
}
}
public void reset() {
synchronized(ratelimiters) {
ratelimiters.clear();
}
}
static EaglerRateLimiter loadConfig(Configuration config) {
int period = config.getInt("period", -1);
int limit = config.getInt("limit", -1);
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();
for(String str : exc) {
exceptions.add(str.toLowerCase());
}
if(period != -1 && limit != -1 && limitLockout != -1 && lockoutDuration != -1) {
return new EaglerRateLimiter(period, limit, limitLockout, lockoutDuration, exceptions);
}else {
return null;
}
}
}

View File

@ -0,0 +1,84 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.util.Collection;
import net.md_5.bungee.config.Configuration;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglerUpdateConfig {
static EaglerUpdateConfig loadConfig(Configuration config) {
boolean blockAllClientUpdates = config.getBoolean("block_all_client_updates", false);
boolean discardLoginPacketCerts = config.getBoolean("discard_login_packet_certs", false);
int certPacketDataRateLimit = config.getInt("cert_packet_data_rate_limit", 524288);
boolean enableEagcertFolder = config.getBoolean("enable_eagcert_folder", true);
boolean downloadLatestCerts = config.getBoolean("download_latest_certs", true);
int checkForUpdatesEvery = config.getInt("check_for_update_every", 900);
Collection<String> downloadCertURLs = (Collection<String>)config.getList("download_certs_from");
return new EaglerUpdateConfig(blockAllClientUpdates, discardLoginPacketCerts, certPacketDataRateLimit,
enableEagcertFolder, downloadLatestCerts, checkForUpdatesEvery, downloadCertURLs);
}
private final boolean blockAllClientUpdates;
private final boolean discardLoginPacketCerts;
private final int certPacketDataRateLimit;
private final boolean enableEagcertFolder;
private final boolean downloadLatestCerts;
private final int checkForUpdatesEvery;
private final Collection<String> downloadCertURLs;
public EaglerUpdateConfig(boolean blockAllClientUpdates, boolean discardLoginPacketCerts,
int certPacketDataRateLimit, boolean enableEagcertFolder, boolean downloadLatestCerts,
int checkForUpdatesEvery, Collection<String> downloadCertURLs) {
this.blockAllClientUpdates = blockAllClientUpdates;
this.discardLoginPacketCerts = discardLoginPacketCerts;
this.certPacketDataRateLimit = certPacketDataRateLimit;
this.enableEagcertFolder = enableEagcertFolder;
this.downloadLatestCerts = downloadLatestCerts;
this.checkForUpdatesEvery = checkForUpdatesEvery;
this.downloadCertURLs = downloadCertURLs;
}
public boolean isBlockAllClientUpdates() {
return blockAllClientUpdates;
}
public boolean isDiscardLoginPacketCerts() {
return discardLoginPacketCerts;
}
public int getCertPacketDataRateLimit() {
return certPacketDataRateLimit;
}
public boolean isEnableEagcertFolder() {
return enableEagcertFolder;
}
public boolean isDownloadLatestCerts() {
return downloadLatestCerts && enableEagcertFolder;
}
public int getCheckForUpdatesEvery() {
return checkForUpdatesEvery;
}
public Collection<String> getDownloadCertURLs() {
return downloadCertURLs;
}
}

View File

@ -0,0 +1,35 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
/**
* 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 MOTDCacheConfiguration {
public final int cacheTTL;
public final boolean cacheServerListAnimation;
public final boolean cacheServerListResults;
public final boolean cacheServerListTrending;
public final boolean cacheServerListPortfolios;
public MOTDCacheConfiguration(int cacheTTL, boolean cacheServerListAnimation, boolean cacheServerListResults,
boolean cacheServerListTrending, boolean cacheServerListPortfolios) {
this.cacheTTL = cacheTTL;
this.cacheServerListAnimation = cacheServerListAnimation;
this.cacheServerListResults = cacheServerListResults;
this.cacheServerListTrending = cacheServerListTrending;
this.cacheServerListPortfolios = cacheServerListPortfolios;
}
}

View File

@ -0,0 +1,20 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
/**
* 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 enum RateLimitStatus {
OK, LIMITED, LIMITED_NOW_LOCKED_OUT, LOCKED_OUT;
}

View File

@ -0,0 +1,81 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import javax.imageio.ImageIO;
/**
* 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.
*
*/
class ServerIconLoader {
static int[] createServerIcon(BufferedImage awtIcon) {
BufferedImage icon = awtIcon;
boolean gotScaled = false;
if(icon.getWidth() != 64 || icon.getHeight() != 64) {
icon = new BufferedImage(64, 64, awtIcon.getType());
Graphics2D g = (Graphics2D) icon.getGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, (awtIcon.getWidth() < 64 || awtIcon.getHeight() < 64) ?
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR : RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setBackground(new Color(0, true));
g.clearRect(0, 0, 64, 64);
int ow = awtIcon.getWidth();
int oh = awtIcon.getHeight();
int nw, nh;
float aspectRatio = (float)oh / (float)ow;
if(aspectRatio >= 1.0f) {
nw = (int)(64 / aspectRatio);
nh = 64;
}else {
nw = 64;
nh = (int)(64 * aspectRatio);
}
g.drawImage(awtIcon, (64 - nw) / 2, (64 - nh) / 2, (64 - nw) / 2 + nw, (64 - nh) / 2 + nh, 0, 0, awtIcon.getWidth(), awtIcon.getHeight(), null);
g.dispose();
gotScaled = true;
}
int[] pxls = icon.getRGB(0, 0, 64, 64, new int[4096], 0, 64);
if(gotScaled) {
for(int i = 0; i < pxls.length; ++i) {
if((pxls[i] & 0xFFFFFF) == 0) {
pxls[i] = 0;
}
}
}
return pxls;
}
static int[] createServerIcon(InputStream f) {
try {
return createServerIcon(ImageIO.read(f));
}catch(Throwable t) {
return null;
}
}
static int[] createServerIcon(File f) {
try {
return createServerIcon(ImageIO.read(f));
}catch(Throwable t) {
return null;
}
}
}

View File

@ -0,0 +1,157 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
import java.util.logging.Level;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.BinaryHttpClient;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.BinaryHttpClient.Response;
import net.md_5.bungee.protocol.Property;
/**
* 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.
*
*/
class VanillaDefaultSkinProfileLoader implements Consumer<Response> {
private class ProfileSkinConsumerImpl implements Consumer<Response> {
private final String uuid;
private ProfileSkinConsumerImpl(String uuid) {
this.uuid = uuid;
}
@Override
public void accept(Response response) {
if(response == null) {
EaglerXBungee.logger().severe("Request error (null)");
doNotify();
}else if(response.exception != null) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception loading vanilla default profile!", response.exception);
doNotify();
}else if(response.code != 200) {
EaglerXBungee.logger().severe("Recieved code " + response.code + " while looking up profile of " + uuid);
doNotify();
}else if (response.data == null) {
EaglerXBungee.logger().severe("Recieved null payload while looking up profile of " + uuid);
doNotify();
}else {
try {
JsonObject json = (new JsonParser()).parse(new String(response.data, StandardCharsets.UTF_8)).getAsJsonObject();
JsonElement propsElement = json.get("properties");
if(propsElement != null) {
JsonArray properties = propsElement.getAsJsonArray();
if(properties.size() > 0) {
for(int i = 0, l = properties.size(); i < l; ++i) {
JsonElement prop = properties.get(i);
if(prop.isJsonObject()) {
JsonObject propObj = prop.getAsJsonObject();
if(propObj.get("name").getAsString().equals("textures")) {
JsonElement value = propObj.get("value");
JsonElement signature = propObj.get("signature");
if(value != null) {
Property newProp = new Property("textures", value.getAsString(),
signature != null ? signature.getAsString() : null);
config.eaglerPlayersVanillaSkinCached = new Property[] { newProp, EaglerBungeeConfig.isEaglerProperty };
}
EaglerXBungee.logger().info("Loaded vanilla profile: " + config.getEaglerPlayersVanillaSkin());
doNotify();
return;
}
}
}
}
}
EaglerXBungee.logger().warning("No skin was found for: " + config.getEaglerPlayersVanillaSkin());
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception processing name to UUID lookup response!", t);
}
doNotify();
}
}
}
private final EaglerBungeeConfig config;
private volatile boolean isLocked = true;
private final Object lock = new Object();
private VanillaDefaultSkinProfileLoader(EaglerBungeeConfig config) {
this.config = config;
}
@Override
public void accept(Response response) {
if(response == null) {
EaglerXBungee.logger().severe("Request error (null)");
doNotify();
}else if(response.exception != null) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception loading vanilla default profile!", response.exception);
doNotify();
}else if(response.code != 200) {
EaglerXBungee.logger().severe("Recieved code " + response.code + " while looking up UUID of " + config.getEaglerPlayersVanillaSkin());
doNotify();
}else if (response.data == null) {
EaglerXBungee.logger().severe("Recieved null payload while looking up UUID of " + config.getEaglerPlayersVanillaSkin());
doNotify();
}else {
try {
JsonObject json = (new JsonParser()).parse(new String(response.data, StandardCharsets.UTF_8)).getAsJsonObject();
String uuid = json.get("id").getAsString();
URI requestURI = URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false");
BinaryHttpClient.asyncRequest("GET", requestURI, new ProfileSkinConsumerImpl(uuid));
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception processing name to UUID lookup response!", t);
doNotify();
}
}
}
private void doNotify() {
synchronized(lock) {
if(isLocked) {
isLocked = false;
lock.notify();
}
}
}
static void lookupVanillaSkinUser(EaglerBungeeConfig config) {
String user = config.getEaglerPlayersVanillaSkin();
EaglerXBungee.logger().info("Loading vanilla profile: " + user);
URI requestURI = URI.create("https://api.mojang.com/users/profiles/minecraft/" + user);
VanillaDefaultSkinProfileLoader loader = new VanillaDefaultSkinProfileLoader(config);
synchronized(loader.lock) {
BinaryHttpClient.asyncRequest("GET", requestURI, loader);
if(loader.isLocked) {
try {
loader.lock.wait(5000l);
} catch (InterruptedException e) {
}
if(loader.isLocked) {
EaglerXBungee.logger().warning("Profile load timed out");
}
}
}
}
}

View File

@ -0,0 +1,205 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.logging.Level;
import org.apache.commons.codec.binary.Base64;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.DefaultAuthSystem;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerAuthConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.CapePackets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.CapeServiceOffline;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinPackets;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SkinService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceService;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice.VoiceSignalPackets;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.event.ServerDisconnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.protocol.Property;
/**
* 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 EaglerPacketEventListener implements Listener {
public static final String FNAW_SKIN_ENABLE_CHANNEL = "EAG|FNAWSEn-1.8";
public final EaglerXBungee plugin;
public EaglerPacketEventListener(EaglerXBungee plugin) {
this.plugin = plugin;
}
@EventHandler
public void onPluginMessage(final PluginMessageEvent event) {
if(event.getSender() instanceof UserConnection) {
final UserConnection player = (UserConnection)event.getSender();
if(player.getPendingConnection() instanceof EaglerInitialHandler) {
if(SkinService.CHANNEL.equals(event.getTag())) {
event.setCancelled(true);
ProxyServer.getInstance().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
try {
SkinPackets.processPacket(event.getData(), player, plugin.getSkinService());
} catch (IOException e) {
event.getSender().disconnect(new TextComponent("Skin packet error!"));
EaglerXBungee.logger().log(Level.SEVERE, "Eagler user \"" + player.getName() + "\" raised an exception handling skins!", e);
}
}
});
}else if(CapeServiceOffline.CHANNEL.equals(event.getTag())) {
event.setCancelled(true);
try {
CapePackets.processPacket(event.getData(), player, plugin.getCapeService());
} catch (IOException e) {
event.getSender().disconnect(new TextComponent("Cape packet error!"));
EaglerXBungee.logger().log(Level.SEVERE, "Eagler user \"" + player.getName() + "\" raised an exception handling capes!", e);
}
}else if(VoiceService.CHANNEL.equals(event.getTag())) {
event.setCancelled(true);
VoiceService svc = plugin.getVoiceService();
if(svc != null && ((EaglerInitialHandler)player.getPendingConnection()).getEaglerListenerConfig().getEnableVoiceChat()) {
try {
VoiceSignalPackets.processPacket(event.getData(), player, svc);
} catch (IOException e) {
event.getSender().disconnect(new TextComponent("Voice signal packet error!"));
EaglerXBungee.logger().log(Level.SEVERE, "Eagler user \"" + player.getName() + "\" raised an exception handling voice signals!", e);
}
}
}
}
}else if(event.getSender() instanceof Server && event.getReceiver() instanceof UserConnection) {
UserConnection player = (UserConnection)event.getReceiver();
if("EAG|GetDomain".equals(event.getTag()) && player.getPendingConnection() instanceof EaglerInitialHandler) {
event.setCancelled(true);
String domain = ((EaglerInitialHandler)player.getPendingConnection()).getOrigin();
if(domain == null) {
((Server)event.getSender()).sendData("EAG|Domain", new byte[] { 0 });
}else {
((Server)event.getSender()).sendData("EAG|Domain", domain.getBytes(StandardCharsets.UTF_8));
}
}
}
}
@EventHandler
@SuppressWarnings("deprecation")
public void onPostLogin(PostLoginEvent event) {
ProxiedPlayer p = event.getPlayer();
if(p instanceof UserConnection) {
UserConnection player = (UserConnection)p;
InitialHandler handler = player.getPendingConnection();
LoginResult res = handler.getLoginProfile();
if(res != null) {
Property[] props = res.getProperties();
if(props.length > 0) {
for(int i = 0; i < props.length; ++i) {
Property pp = props[i];
if(pp.getName().equals("textures")) {
try {
String jsonStr = SkinPackets.bytesToAscii(Base64.decodeBase64(pp.getValue()));
JsonObject json = (new JsonParser()).parse(jsonStr).getAsJsonObject();
JsonObject skinObj = json.getAsJsonObject("SKIN");
if(skinObj != null) {
JsonElement url = json.get("url");
if(url != null) {
String urlStr = SkinService.sanitizeTextureURL(url.getAsString());
plugin.getSkinService().registerTextureToPlayerAssociation(urlStr, player.getUniqueId());
}
}
}catch(Throwable t) {
}
}
}
}
}
EaglerAuthConfig authConf = plugin.getConfig().getAuthConfig();
if(authConf.isEnableAuthentication() && authConf.isUseBuiltInAuthentication()) {
DefaultAuthSystem srv = plugin.getAuthService();
if(srv != null) {
srv.handleVanillaLogin(event);
}
}
}
}
@EventHandler
public void onConnectionLost(PlayerDisconnectEvent event) {
UUID uuid = event.getPlayer().getUniqueId();
plugin.getSkinService().unregisterPlayer(uuid);
plugin.getCapeService().unregisterPlayer(uuid);
if(event.getPlayer() instanceof UserConnection) {
UserConnection player = (UserConnection)event.getPlayer();
if((player.getPendingConnection() instanceof EaglerInitialHandler)
&& ((EaglerInitialHandler) player.getPendingConnection()).getEaglerListenerConfig()
.getEnableVoiceChat()) {
plugin.getVoiceService().handlePlayerLoggedOut(player);
}
}
}
@EventHandler
public void onServerConnected(ServerConnectedEvent event) {
if(event.getPlayer() instanceof UserConnection) {
UserConnection player = (UserConnection)event.getPlayer();
if(player.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler handler = (EaglerInitialHandler) player.getPendingConnection();
ServerInfo sv = event.getServer().getInfo();
boolean fnawSkins = !plugin.getConfig().getDisableFNAWSkinsEverywhere() && !plugin.getConfig().getDisableFNAWSkinsOnServersSet().contains(sv.getName());
if(fnawSkins != handler.currentFNAWSkinEnableStatus) {
handler.currentFNAWSkinEnableStatus = fnawSkins;
player.sendData(FNAW_SKIN_ENABLE_CHANNEL, new byte[] { fnawSkins ? (byte)1 : (byte)0 });
}
if(handler.getEaglerListenerConfig().getEnableVoiceChat()) {
plugin.getVoiceService().handleServerConnected(player, sv);
}
}
}
}
@EventHandler
public void onServerDisconnected(ServerDisconnectEvent event) {
if(event.getPlayer() instanceof UserConnection) {
UserConnection player = (UserConnection)event.getPlayer();
if((player.getPendingConnection() instanceof EaglerInitialHandler)
&& ((EaglerInitialHandler) player.getPendingConnection()).getEaglerListenerConfig()
.getEnableVoiceChat()) {
plugin.getVoiceService().handleServerDisconnected(player, event.getTarget());
}
}
}
}

View File

@ -0,0 +1,36 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.handlers;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.md_5.bungee.api.event.ProxyReloadEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
/**
* 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 EaglerPluginEventListener implements Listener {
public final EaglerXBungee plugin;
public EaglerPluginEventListener(EaglerXBungee plugin) {
this.plugin = plugin;
}
@EventHandler
public void onReload(ProxyReloadEvent evt) {
plugin.reload();
}
}

View File

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

View File

@ -0,0 +1,43 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import io.netty.channel.Channel;
import net.md_5.bungee.UserConnection;
/**
* 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 EaglerConnectionInstance {
public final Channel channel;
public final long creationTime;
public boolean hasBeenForwarded = false;
public long lastServerPingPacket;
public long lastClientPingPacket;
public long lastClientPongPacket;
public boolean isWebSocket = false;
public boolean isRegularHttp = false;
public UserConnection userConnection = null;
public EaglerConnectionInstance(Channel channel) {
this.channel = channel;
this.creationTime = this.lastServerPingPacket = this.lastClientPingPacket =
this.lastClientPongPacket = System.currentTimeMillis();
}
}

View File

@ -0,0 +1,269 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.TIntHashSet;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.SimpleRateLimiter;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.EncryptionResponse;
import net.md_5.bungee.protocol.packet.Handshake;
import net.md_5.bungee.protocol.packet.LegacyHandshake;
import net.md_5.bungee.protocol.packet.LegacyPing;
import net.md_5.bungee.protocol.packet.LoginPayloadResponse;
import net.md_5.bungee.protocol.packet.LoginRequest;
import net.md_5.bungee.protocol.packet.PingPacket;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.StatusRequest;
/**
* 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 EaglerInitialHandler extends InitialHandler {
public static class ClientCertificateHolder {
public final byte[] data;
public final int hash;
public ClientCertificateHolder(byte[] data, int hash) {
this.data = data;
this.hash = hash;
}
}
private final int gameProtocolVersion;
private final String username;
private final UUID playerUUID;
private LoginResult loginResult;
private final InetSocketAddress eaglerAddress;
private final InetSocketAddress virtualHost;
private final Unsafe eaglerUnsafe;
public final SimpleRateLimiter skinLookupRateLimiter;
public final SimpleRateLimiter skinUUIDLookupRateLimiter;
public final SimpleRateLimiter skinTextureDownloadRateLimiter;
public final SimpleRateLimiter capeLookupRateLimiter;
public final SimpleRateLimiter voiceConnectRateLimiter;
public final String origin;
public final ClientCertificateHolder clientCertificate;
public final Set<ClientCertificateHolder> certificatesToSend;
public final TIntSet certificatesSent;
public boolean currentFNAWSkinEnableStatus = true;
private static final Property[] NO_PROPERTIES = new Property[0];
public EaglerInitialHandler(BungeeCord bungee, EaglerListenerConfig listener, final ChannelWrapper ch,
int gameProtocolVersion, String username, UUID playerUUID, InetSocketAddress address, String host,
String origin, ClientCertificateHolder clientCertificate) {
super(bungee, listener);
this.gameProtocolVersion = gameProtocolVersion;
this.username = username;
this.playerUUID = playerUUID;
this.eaglerAddress = address;
this.origin = origin;
this.skinLookupRateLimiter = new SimpleRateLimiter();
this.skinUUIDLookupRateLimiter = new SimpleRateLimiter();
this.skinTextureDownloadRateLimiter = new SimpleRateLimiter();
this.capeLookupRateLimiter = new SimpleRateLimiter();
this.voiceConnectRateLimiter = new SimpleRateLimiter();
this.clientCertificate = clientCertificate;
this.certificatesToSend = new HashSet();
this.certificatesSent = new TIntHashSet();
if(clientCertificate != null) {
this.certificatesSent.add(clientCertificate.hashCode());
}
if(host == null) host = "";
int port = 25565;
if(host.contains(":")) {
int ind = host.lastIndexOf(':');
try {
port = Integer.parseInt(host.substring(ind + 1));
host = host.substring(0, ind);
} catch (NumberFormatException e) {
//
}
}
this.virtualHost = InetSocketAddress.createUnresolved(host, port);
this.eaglerUnsafe = new Unsafe() {
@Override
public void sendPacket(DefinedPacket arg0) {
ch.getHandle().writeAndFlush(arg0);
}
};
Property[] profileProperties = NO_PROPERTIES;
if(EaglerXBungee.getEagler().getConfig().getEnableIsEaglerPlayerProperty()) {
profileProperties = new Property[] { EaglerBungeeConfig.isEaglerProperty };
}
setLoginProfile(new LoginResult(playerUUID.toString(), username, profileProperties));
try {
super.connected(ch);
} catch (Exception e) {
}
}
void setLoginProfile(LoginResult obj) {
this.loginResult = obj;
try {
Field f = InitialHandler.class.getDeclaredField("loginProfile");
f.setAccessible(true);
f.set(this, obj);
}catch(Throwable t) {
}
}
@Override
public void handle(PacketWrapper packet) throws Exception {
}
@Override
public void handle(PluginMessage pluginMessage) throws Exception {
}
@Override
public void handle(LegacyHandshake legacyHandshake) throws Exception {
}
@Override
public void handle(LegacyPing ping) throws Exception {
}
@Override
public void handle(StatusRequest statusRequest) throws Exception {
}
@Override
public void handle(PingPacket ping) throws Exception {
}
@Override
public void handle(Handshake handshake) throws Exception {
}
@Override
public void handle(LoginRequest loginRequest) throws Exception {
}
@Override
public void handle(EncryptionResponse encryptResponse) throws Exception {
}
@Override
public void handle(LoginPayloadResponse response) throws Exception {
}
@Override
public void disconnect(String reason) {
super.disconnect(reason);
}
@Override
public void disconnect(BaseComponent... reason) {
super.disconnect(reason);
}
@Override
public void disconnect(BaseComponent reason) {
super.disconnect(reason);
}
@Override
public String getName() {
return username;
}
@Override
public int getVersion() {
return gameProtocolVersion;
}
@Override
public Handshake getHandshake() {
return new Handshake(gameProtocolVersion, virtualHost.getHostName(), virtualHost.getPort(),
gameProtocolVersion);
}
@Override
public LoginRequest getLoginRequest() {
throw new UnsupportedOperationException("A plugin attempted to retrieve the LoginRequest packet from an EaglercraftX connection, "
+ "which is not supported because Eaglercraft does not use online mode encryption. Please analyze the stack trace of this "
+ "exception and reconfigure or remove the offending plugin to fix this issue.");
}
@Override
public PluginMessage getBrandMessage() {
String brand = "EaglercraftX";
byte[] pkt = new byte[brand.length() + 1];
pkt[0] = (byte)brand.length();
System.arraycopy(brand.getBytes(StandardCharsets.US_ASCII), 0, pkt, 1, brand.length());
return new PluginMessage("MC|Brand", pkt, true);
}
@Override
public UUID getUniqueId() {
return playerUUID;
}
@Override
public UUID getOfflineId() {
return playerUUID;
}
@Override
public String getUUID() {
return playerUUID.toString().replace("-", "");
}
@Override
public LoginResult getLoginProfile() {
return loginResult;
}
@Override
public InetSocketAddress getVirtualHost() {
return virtualHost;
}
@Override
public SocketAddress getSocketAddress() {
return eaglerAddress;
}
@Override
public Unsafe unsafe() {
return eaglerUnsafe;
}
public String getOrigin() {
return origin;
}
public EaglerListenerConfig getEaglerListenerConfig() {
return (EaglerListenerConfig)getListener();
}
}

View File

@ -0,0 +1,33 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
/**
* 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 EaglerMinecraftByteBufEncoder extends MessageToMessageEncoder<ByteBuf> {
@Override
protected void encode(ChannelHandlerContext var1, ByteBuf var2, List<Object> var3) throws Exception {
var3.add(new BinaryWebSocketFrame(Unpooled.copiedBuffer(var2)));
}
}

View File

@ -0,0 +1,148 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
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_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerBungeeProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerProtocolAccessProxy;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.ProtocolConstants.Direction;
/**
* 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 EaglerMinecraftDecoder extends MessageToMessageDecoder<WebSocketFrame> {
private EaglerBungeeProtocol protocol;
private final boolean server;
private int protocolVersion;
private static Constructor<PacketWrapper> packetWrapperConstructor = null;
@Override
protected void decode(ChannelHandlerContext ctx, WebSocketFrame frame, List<Object> out) throws Exception {
if(!ctx.channel().isActive()) {
return;
}
EaglerConnectionInstance con = ctx.channel().attr(EaglerPipeline.CONNECTION_INSTANCE).get();
long millis = System.currentTimeMillis();
if(frame instanceof BinaryWebSocketFrame) {
BinaryWebSocketFrame in = (BinaryWebSocketFrame) frame;
ByteBuf buf = in.content();
int pktId = DefinedPacket.readVarInt(buf);
DefinedPacket pkt = EaglerProtocolAccessProxy.createPacket(protocol, protocolVersion, pktId, server);
Protocol bungeeProtocol = null;
switch(this.protocol) {
case GAME:
bungeeProtocol = Protocol.GAME;
break;
case HANDSHAKE:
bungeeProtocol = Protocol.HANDSHAKE;
break;
case LOGIN:
bungeeProtocol = Protocol.LOGIN;
break;
case STATUS:
bungeeProtocol = Protocol.STATUS;
}
if(pkt != null) {
pkt.read(buf, server ? Direction.TO_CLIENT : Direction.TO_SERVER, protocolVersion);
if(buf.isReadable()) {
EaglerXBungee.logger().severe("[DECODER][" + ctx.channel().remoteAddress() + "] Packet " +
pkt.getClass().getSimpleName() + " had extra bytes! (" + buf.readableBytes() + ")");
}else {
out.add(this.wrapPacket(pkt, buf, bungeeProtocol));
}
}else {
out.add(this.wrapPacket(null, buf, bungeeProtocol));
}
}else if(frame instanceof PingWebSocketFrame) {
if(millis - con.lastClientPingPacket > 500l) {
ctx.write(new PongWebSocketFrame());
con.lastClientPingPacket = millis;
}
}else if(frame instanceof PongWebSocketFrame) {
con.lastClientPongPacket = millis;
}else {
ctx.close();
}
}
public EaglerMinecraftDecoder(final EaglerBungeeProtocol protocol, final boolean server, final int protocolVersion) {
this.protocol = protocol;
this.server = server;
this.protocolVersion = protocolVersion;
}
public void setProtocol(final EaglerBungeeProtocol protocol) {
this.protocol = protocol;
}
public void setProtocolVersion(final int protocolVersion) {
this.protocolVersion = protocolVersion;
}
private PacketWrapper wrapPacket(DefinedPacket packet, ByteBuf buf, Protocol protocol) {
ByteBuf cbuf = null;
PacketWrapper var7;
try {
cbuf = buf.copy(0, buf.writerIndex());
PacketWrapper pkt;
if (packetWrapperConstructor != null) {
try {
pkt = packetWrapperConstructor.newInstance(packet, cbuf);
cbuf = null;
return pkt;
} catch (IllegalAccessException | InvocationTargetException | InstantiationException var14) {
throw new RuntimeException(var14);
}
}
try {
pkt = new PacketWrapper(packet, cbuf, protocol);
cbuf = null;
return pkt;
} catch (NoSuchMethodError var15) {
try {
packetWrapperConstructor = PacketWrapper.class.getDeclaredConstructor(DefinedPacket.class, ByteBuf.class);
pkt = packetWrapperConstructor.newInstance(packet, cbuf);
cbuf = null;
var7 = pkt;
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException var13) {
throw new RuntimeException(var13);
}
}
} finally {
if (cbuf != null) {
cbuf.release();
}
}
return var7;
}
}

View File

@ -0,0 +1,95 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.lang.reflect.Method;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerBungeeProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol.EaglerProtocolAccessProxy;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.ProtocolConstants.Direction;
/**
* 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 EaglerMinecraftEncoder extends MessageToMessageEncoder<DefinedPacket> {
private EaglerBungeeProtocol protocol;
private boolean server;
private int protocolVersion;
private static Method meth = null;
@Override
protected void encode(ChannelHandlerContext ctx, DefinedPacket msg, List<Object> out) throws Exception {
Protocol bungeeProtocol = null;
switch(this.protocol) {
case GAME:
bungeeProtocol = Protocol.GAME;
break;
case HANDSHAKE:
bungeeProtocol = Protocol.HANDSHAKE;
break;
case LOGIN:
bungeeProtocol = Protocol.LOGIN;
break;
case STATUS:
bungeeProtocol = Protocol.STATUS;
}
ByteBuf buf = Unpooled.buffer();
int pk = EaglerProtocolAccessProxy.getPacketId(protocol, protocolVersion, msg, server);
DefinedPacket.writeVarInt(pk, buf);
try {
msg.write(buf, bungeeProtocol, server ? Direction.TO_CLIENT : Direction.TO_SERVER, protocolVersion);
} catch (NoSuchMethodError e) {
try {
if (meth == null) {
meth = DefinedPacket.class.getDeclaredMethod("write", ByteBuf.class, Direction.class, int.class);
}
meth.invoke(msg, buf, server ? Direction.TO_CLIENT : Direction.TO_SERVER, protocolVersion);
} catch (Exception e1) {
buf.release();
buf = Unpooled.EMPTY_BUFFER;
}
} catch (Exception e) {
buf.release();
buf = Unpooled.EMPTY_BUFFER;
}
out.add(new BinaryWebSocketFrame(buf));
}
public EaglerMinecraftEncoder(final EaglerBungeeProtocol protocol, final boolean server, final int protocolVersion) {
this.protocol = protocol;
this.server = server;
this.protocolVersion = protocolVersion;
}
public void setProtocol(final EaglerBungeeProtocol protocol) {
this.protocol = protocol;
}
public void setProtocolVersion(final int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public EaglerBungeeProtocol getProtocol() {
return this.protocol;
}
}

View File

@ -0,0 +1,32 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.util.List;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import net.md_5.bungee.protocol.PacketWrapper;
/**
* 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 EaglerMinecraftWrappedEncoder extends MessageToMessageEncoder<PacketWrapper> {
@Override
protected void encode(ChannelHandlerContext ctx, PacketWrapper msg, List<Object> out) throws Exception {
out.add(new BinaryWebSocketFrame(msg.buf));
}
}

View File

@ -0,0 +1,185 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TimerTask;
import java.util.logging.Logger;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.DeflateFrameServerExtensionHandshaker;
import io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker;
import io.netty.util.AttributeKey;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler.ClientCertificateHolder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer;
/**
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglerPipeline {
public static final AttributeKey<EaglerListenerConfig> LISTENER = AttributeKey.valueOf("ListenerInfo");
public static final AttributeKey<InetSocketAddress> LOCAL_ADDRESS = AttributeKey.valueOf("LocalAddress");
public static final AttributeKey<EaglerConnectionInstance> CONNECTION_INSTANCE = AttributeKey.valueOf("EaglerConnectionInstance");
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 Collection<Channel> openChannels = new LinkedList();
public static final String UPDATE_CERT_CHANNEL = "EAG|UpdateCert-1.8";
public static final TimerTask closeInactive = new TimerTask() {
@Override
public void run() {
Logger log = EaglerXBungee.logger();
try {
EaglerBungeeConfig conf = EaglerXBungee.getEagler().getConfig();
long handshakeTimeout = conf.getWebsocketHandshakeTimeout();
long keepAliveTimeout = conf.getWebsocketKeepAliveTimeout();
List<Channel> channelsList;
synchronized(openChannels) {
long millis = System.currentTimeMillis();
Iterator<Channel> channelIterator = openChannels.iterator();
while(channelIterator.hasNext()) {
Channel c = channelIterator.next();
final EaglerConnectionInstance i = c.attr(EaglerPipeline.CONNECTION_INSTANCE).get();
long handshakeTimeoutForConnection = 500l;
if(i.isRegularHttp) handshakeTimeoutForConnection = 10000l;
if(i.isWebSocket) handshakeTimeoutForConnection = handshakeTimeout;
if(i == null || (!i.hasBeenForwarded && millis - i.creationTime > handshakeTimeoutForConnection)
|| millis - i.lastClientPongPacket > keepAliveTimeout || !c.isActive()) {
if(c.isActive()) {
c.close();
}
channelIterator.remove();
}else {
long pingRate = 5000l;
if(pingRate + 700l > keepAliveTimeout) {
pingRate = keepAliveTimeout - 500l;
if(pingRate < 500l) {
keepAliveTimeout = 500l;
}
}
if(millis - i.lastServerPingPacket > pingRate) {
i.lastServerPingPacket = millis;
c.write(new PingWebSocketFrame());
}
}
}
channelsList = new ArrayList(openChannels);
}
for(EaglerListenerConfig lst : conf.getServerListeners()) {
HttpWebServer srv = lst.getWebServer();
if(srv != null) {
try {
srv.flushCache();
}catch(Throwable t) {
log.severe("Failed to flush web server cache for: " + lst.getAddress().toString());
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;
}
EaglerInitialHandler i = (EaglerInitialHandler)conn.userConnection.getPendingConnection();
ClientCertificateHolder certHolder = null;
synchronized(i.certificatesToSend) {
if(i.certificatesToSend.size() > 0) {
Iterator<ClientCertificateHolder> itr = i.certificatesToSend.iterator();
certHolder = itr.next();
itr.remove();
}
}
if(certHolder != null) {
int identityHash = certHolder.hashCode();
boolean bb;
synchronized(i.certificatesSent) {
bb = i.certificatesSent.add(identityHash);
}
if(bb) {
conn.userConnection.sendData(UPDATE_CERT_CHANNEL, certHolder.data);
sizeTracker += certHolder.data.length;
if(sizeTracker > (conf.getUpdateConfig().getCertPacketDataRateLimit() / 4)) {
break;
}
}
}
}
EaglerUpdateSvc.updateTick();
}
}catch(Throwable t) {
log.severe("Exception in thread \"" + Thread.currentThread().getName() + "\"! " + t.toString());
t.printStackTrace();
}
}
};
public static final ChannelInitializer<Channel> SERVER_CHILD = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("HttpServerCodec", new HttpServerCodec());
pipeline.addLast("HttpObjectAggregator", new HttpObjectAggregator(65535));
int compressionLevel = EaglerXBungee.getEagler().getConfig().getHttpWebsocketCompressionLevel();
if(compressionLevel > 0) {
if(compressionLevel > 9) {
compressionLevel = 9;
}
DeflateFrameServerExtensionHandshaker deflateExtensionHandshaker = new DeflateFrameServerExtensionHandshaker(
compressionLevel);
PerMessageDeflateServerExtensionHandshaker perMessageDeflateExtensionHandshaker = new PerMessageDeflateServerExtensionHandshaker(
compressionLevel, ZlibCodecFactory.isSupportingWindowSizeAndMemLevel(),
PerMessageDeflateServerExtensionHandshaker.MAX_WINDOW_SIZE, false, false);
pipeline.addLast("HttpCompressionHandler", new WebSocketServerExtensionHandler(deflateExtensionHandshaker,
perMessageDeflateExtensionHandshaker));
}
pipeline.addLast("HttpHandshakeHandler", new HttpHandshakeHandler(channel.attr(LISTENER).get()));
channel.attr(CONNECTION_INSTANCE).set(new EaglerConnectionInstance(channel));
synchronized(openChannels) {
openChannels.add(channel);
}
}
};
public static void closeChannel(Channel channel) {
synchronized(openChannels) {
openChannels.remove(channel);
}
}
}

View File

@ -0,0 +1,297 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
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 java.util.Set;
import java.util.logging.Logger;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.auth.SHA1Digest;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerUpdateConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler.ClientCertificateHolder;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglerUpdateSvc {
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;
private final long lastModified;
public CachedClientCertificate(ClientCertificateHolder cert, long lastModified) {
this.cert = cert;
this.lastModified = lastModified;
}
}
private static long lastDownload = 0l;
private static long lastEnumerate = 0l;
public static void updateTick() {
Logger log = EaglerXBungee.logger();
long millis = System.currentTimeMillis();
EaglerUpdateConfig conf = EaglerXBungee.getEagler().getConfig().getUpdateConfig();
if(conf.isDownloadLatestCerts() && millis - lastDownload > (long)conf.getCheckForUpdatesEvery() * 1000l) {
lastDownload = millis;
lastEnumerate = 0l;
try {
downloadUpdates();
}catch(Throwable t) {
log.severe("Uncaught exception downloading certificates!");
t.printStackTrace();
}
millis = System.currentTimeMillis();
}
if(conf.isEnableEagcertFolder() && millis - lastEnumerate > 5000l) {
lastEnumerate = millis;
try {
enumerateEagcertDirectory();
}catch(Throwable t) {
log.severe("Uncaught exception reading eagcert directory!");
t.printStackTrace();
}
}
}
private static void downloadUpdates() throws Throwable {
Logger log = EaglerXBungee.logger();
EaglerUpdateConfig conf = EaglerXBungee.getEagler().getConfig().getUpdateConfig();
File eagcert = new File(EaglerXBungee.getEagler().getDataFolder(), "eagcert");
if(!eagcert.isDirectory()) {
if(!eagcert.mkdirs()) {
log.severe("Could not create directory: " + eagcert.getAbsolutePath());
return;
}
}
Set<String> filenames = new HashSet();
for(String str : conf.getDownloadCertURLs()) {
try {
URL url = new URL(str);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setDoInput(true);
con.setDoOutput(false);
con.setRequestMethod("GET");
con.setConnectTimeout(30000);
con.setReadTimeout(30000);
con.setRequestProperty("User-Agent", "Mozilla/5.0 EaglerXBungee/" + EaglerXBungee.getEagler().getDescription().getVersion());
con.connect();
int code = con.getResponseCode();
if(code / 100 != 2) {
con.disconnect();
throw new IOException("Response code was " + code);
}
ByteArrayOutputStream bao = new ByteArrayOutputStream(32767);
try(InputStream is = con.getInputStream()) {
byte[] readBuffer = new byte[1024];
int c;
while((c = is.read(readBuffer, 0, 1024)) != -1) {
bao.write(readBuffer, 0, c);
}
}
byte[] done = bao.toByteArray();
SHA1Digest digest = new SHA1Digest();
digest.update(done, 0, done.length);
byte[] hash = new byte[20];
digest.doFinal(hash, 0);
char[] hexchars = new char[40];
for(int i = 0; i < 20; ++i) {
hexchars[i << 1] = hex[(hash[i] >> 4) & 15];
hexchars[(i << 1) + 1] = hex[hash[i] & 15];
}
String strr = "$dl." + new String(hexchars) + ".cert";
filenames.add(strr);
File cacheFile = new File(eagcert, strr);
if(cacheFile.exists()) {
continue; // no change
}
try(OutputStream os = new FileOutputStream(cacheFile)) {
os.write(done);
}
log.info("Downloading new certificate: " + str);
}catch(Throwable t) {
log.severe("Failed to download certificate: " + str);
log.severe("Reason: " + t.toString());
}
}
long millis = System.currentTimeMillis();
File[] dirList = eagcert.listFiles();
for(int i = 0; i < dirList.length; ++i) {
File f = dirList[i];
String n = f.getName();
if(!n.startsWith("$dl.")) {
continue;
}
if(millis - f.lastModified() > 86400000l && !filenames.contains(n)) {
log.warning("Deleting stale certificate: " + n);
if(!f.delete()) {
log.severe("Failed to delete: " + n);
}
}
}
}
private static final char[] hex = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private static void enumerateEagcertDirectory() throws Throwable {
Logger log = EaglerXBungee.logger();
File eagcert = new File(EaglerXBungee.getEagler().getDataFolder(), "eagcert");
if(!eagcert.isDirectory()) {
if(!eagcert.mkdirs()) {
log.severe("Could not create directory: " + eagcert.getAbsolutePath());
return;
}
}
boolean dirty = false;
File[] dirList = eagcert.listFiles();
Set<String> existingFiles = new HashSet();
for(int i = 0; i < dirList.length; ++i) {
File f = dirList[i];
String n = f.getName();
long lastModified = f.lastModified();
existingFiles.add(n);
CachedClientCertificate cc = certsCache.get(n);
if(cc != null) {
if(cc.lastModified != lastModified) {
try {
byte[] fileData = new byte[(int)f.length()];
if(fileData.length > 0xFFFF) {
throw new IOException("File is too long! Max: 65535 bytes");
}
try(FileInputStream fis = new FileInputStream(f)) {
for(int j = 0, k; (k = fis.read(fileData, j, fileData.length - j)) != -1 && j < fileData.length; j += k);
}
certsCache.remove(n);
ClientCertificateHolder ch = tryMakeHolder(fileData);
certsCache.put(n, new CachedClientCertificate(ch, lastModified));
dirty = true;
sendCertificateToPlayers(ch);
log.info("Reloaded certificate: " + f.getAbsolutePath());
}catch(IOException ex) {
log.severe("Failed to read: " + f.getAbsolutePath());
log.severe("Reason: " + ex.toString());
}
}
continue;
}
try {
byte[] fileData = new byte[(int)f.length()];
if(fileData.length > 0xFFFF) {
throw new IOException("File is too long! Max: 65535 bytes");
}
try(FileInputStream fis = new FileInputStream(f)) {
for(int j = 0, k; j < fileData.length && (k = fis.read(fileData, j, fileData.length - j)) != -1; j += k);
}
ClientCertificateHolder ch = tryMakeHolder(fileData);
certsCache.put(n, new CachedClientCertificate(ch, lastModified));
dirty = true;
sendCertificateToPlayers(ch);
log.info("Loaded certificate: " + f.getAbsolutePath());
}catch(IOException ex) {
log.severe("Failed to read: " + f.getAbsolutePath());
log.severe("Reason: " + ex.toString());
}
}
Iterator<String> itr = certsCache.keySet().iterator();
while(itr.hasNext()) {
String etr = itr.next();
if(!existingFiles.contains(etr)) {
itr.remove();
dirty = true;
log.warning("Certificate was deleted: " + etr);
}
}
if(dirty) {
remakeCertsList();
}
}
private static void remakeCertsList() {
synchronized(certs) {
certs.clear();
for(CachedClientCertificate cc : certsCache.values()) {
certs.add(cc.cert);
}
}
}
public static List<ClientCertificateHolder> getCertList() {
return certs;
}
public static ClientCertificateHolder tryMakeHolder(byte[] data) {
int hash = Arrays.hashCode(data);
ClientCertificateHolder ret = tryGetHolder(data, hash);
if(ret == null) {
ret = new ClientCertificateHolder(data, hash);
}
return ret;
}
public static ClientCertificateHolder tryGetHolder(byte[] data, int hash) {
synchronized(certs) {
for(ClientCertificateHolder cc : certs) {
if(cc.hash == hash && Arrays.equals(cc.data, data)) {
return cc;
}
}
}
for(ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
if(p.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler pp = (EaglerInitialHandler)p.getPendingConnection();
if(pp.clientCertificate != null && pp.clientCertificate.hash == hash && Arrays.equals(pp.clientCertificate.data, data)) {
return pp.clientCertificate;
}
}
}
return null;
}
public static void sendCertificateToPlayers(ClientCertificateHolder holder) {
for(ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
if(p.getPendingConnection() instanceof EaglerInitialHandler) {
EaglerInitialHandler pp = (EaglerInitialHandler)p.getPendingConnection();
boolean bb;
synchronized(pp.certificatesSent) {
bb = pp.certificatesSent.contains(holder.hashCode());
}
if(!bb) {
Set<ClientCertificateHolder> set = pp.certificatesToSend;
synchronized(set) {
set.add(holder);
}
}
}
}
}
}

View File

@ -0,0 +1,49 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
/**
* 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 HandshakePacketTypes {
public static final String AUTHENTICATION_REQUIRED = "Authentication Required:";
public static final int PROTOCOL_CLIENT_VERSION = 0x01;
public static final int PROTOCOL_SERVER_VERSION = 0x02;
public static final int PROTOCOL_VERSION_MISMATCH = 0x03;
public static final int PROTOCOL_CLIENT_REQUEST_LOGIN = 0x04;
public static final int PROTOCOL_SERVER_ALLOW_LOGIN = 0x05;
public static final int PROTOCOL_SERVER_DENY_LOGIN = 0x06;
public static final int PROTOCOL_CLIENT_PROFILE_DATA = 0x07;
public static final int PROTOCOL_CLIENT_FINISH_LOGIN = 0x08;
public static final int PROTOCOL_SERVER_FINISH_LOGIN = 0x09;
public static final int PROTOCOL_SERVER_ERROR = 0xFF;
public static final int STATE_OPENED = 0x00;
public static final int STATE_CLIENT_VERSION = 0x01;
public static final int STATE_CLIENT_LOGIN = 0x02;
public static final int STATE_CLIENT_COMPLETE = 0x03;
public static final int STATE_STALLING = 0xFF;
public static final int SERVER_ERROR_UNKNOWN_PACKET = 0x01;
public static final int SERVER_ERROR_INVALID_PACKET = 0x02;
public static final int SERVER_ERROR_WRONG_PACKET = 0x03;
public static final int SERVER_ERROR_EXCESSIVE_PROFILE_DATA = 0x04;
public static final int SERVER_ERROR_DUPLICATE_PROFILE_DATA = 0x05;
public static final int SERVER_ERROR_RATELIMIT_BLOCKED = 0x06;
public static final int SERVER_ERROR_RATELIMIT_LOCKED = 0x07;
public static final int SERVER_ERROR_CUSTOM_MESSAGE = 0x08;
public static final int SERVER_ERROR_AUTHENTICATION_REQUIRED = 0x09;
}

View File

@ -0,0 +1,185 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerRateLimiter;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.RateLimitStatus;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpMemoryCache;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web.HttpWebServer;
/**
* 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 HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
private static final byte[] error429Bytes = "<h3>429 Too Many Requests<br /><small>(Try again later)</small></h3>".getBytes(StandardCharsets.UTF_8);
private final EaglerListenerConfig conf;
public HttpHandshakeHandler(EaglerListenerConfig conf) {
this.conf = conf;
}
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
EaglerConnectionInstance pingTracker = ctx.channel().attr(EaglerPipeline.CONNECTION_INSTANCE).get();
HttpRequest req = (HttpRequest) msg;
HttpHeaders headers = req.headers();
String rateLimitHost = null;
if(conf.isForwardIp()) {
String str = headers.get(conf.getForwardIpHeader());
if(str != null) {
rateLimitHost = str.split(",", 2)[0];
try {
ctx.channel().attr(EaglerPipeline.REAL_ADDRESS).set(InetAddress.getByName(rateLimitHost));
}catch(UnknownHostException ex) {
EaglerXBungee.logger().warning("[" + ctx.channel().remoteAddress() + "]: Connected with an invalid '" + conf.getForwardIpHeader() + "' header, disconnecting...");
ctx.close();
return;
}
}else {
EaglerXBungee.logger().warning("[" + ctx.channel().remoteAddress() + "]: Connected without a '" + conf.getForwardIpHeader() + "' header, disconnecting...");
ctx.close();
return;
}
}else {
SocketAddress addr = ctx.channel().remoteAddress();
if(addr instanceof InetSocketAddress) {
rateLimitHost = ((InetSocketAddress) addr).getAddress().getHostAddress();
}
}
EaglerRateLimiter ipRateLimiter = conf.getRatelimitIp();
RateLimitStatus ipRateLimit = RateLimitStatus.OK;
if(ipRateLimiter != null && rateLimitHost != null) {
ipRateLimit = ipRateLimiter.rateLimit(rateLimitHost);
}
if(ipRateLimit == RateLimitStatus.LOCKED_OUT) {
ctx.close();
return;
}
if(headers.get(HttpHeaderNames.CONNECTION) != null && headers.get(HttpHeaderNames.CONNECTION).toLowerCase().contains("upgrade") &&
"websocket".equalsIgnoreCase(headers.get(HttpHeaderNames.UPGRADE))) {
String origin = headers.get(HttpHeaderNames.ORIGIN);
if(origin != null) {
ctx.channel().attr(EaglerPipeline.ORIGIN).set(origin);
}
//TODO: origin blacklist
if(ipRateLimit == RateLimitStatus.OK) {
ctx.channel().attr(EaglerPipeline.HOST).set(headers.get(HttpHeaderNames.HOST));
ctx.pipeline().replace(this, "HttpWebSocketHandler", new HttpWebSocketHandler(conf));
}
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
"ws://" + headers.get(HttpHeaderNames.HOST) + req.uri(), null, true, 0xFFFFF);
WebSocketServerHandshaker hs = wsFactory.newHandshaker(req);
if(hs != null) {
pingTracker.isWebSocket = true;
ChannelFuture future = hs.handshake(ctx.channel(), req);
if(ipRateLimit != RateLimitStatus.OK) {
final RateLimitStatus rateLimitTypeFinal = ipRateLimit;
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture paramF) throws Exception {
ctx.writeAndFlush(new TextWebSocketFrame(
rateLimitTypeFinal == RateLimitStatus.LIMITED_NOW_LOCKED_OUT ? "LOCKED" : "BLOCKED"))
.addListener(ChannelFutureListener.CLOSE);
}
});
}
}else {
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()).addListener(ChannelFutureListener.CLOSE);
}
}else {
if(ipRateLimit != RateLimitStatus.OK) {
ByteBuf error429Buffer = ctx.alloc().buffer(error429Bytes.length, error429Bytes.length);
error429Buffer.writeBytes(error429Bytes);
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.TOO_MANY_REQUESTS, error429Buffer);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
return;
}
pingTracker.isRegularHttp = true;
HttpWebServer srv = conf.getWebServer();
if(srv != null) {
String uri = req.uri();
if(uri.startsWith("/")) {
uri = uri.substring(1);
}
int j = uri.indexOf('?');
if(j != -1) {
uri = uri.substring(0, j);
}
HttpMemoryCache ch = srv.retrieveFile(uri);
if(ch != null) {
ctx.writeAndFlush(ch.createHTTPResponse()).addListener(ChannelFutureListener.CLOSE);
}else {
ctx.writeAndFlush(HttpWebServer.getWebSocket404().createHTTPResponse(HttpResponseStatus.NOT_FOUND))
.addListener(ChannelFutureListener.CLOSE);
}
}else {
ctx.writeAndFlush(HttpWebServer.getWebSocket404().createHTTPResponse(HttpResponseStatus.NOT_FOUND))
.addListener(ChannelFutureListener.CLOSE);
}
}
}else {
ctx.close();
}
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (ctx.channel().isActive()) {
EaglerXBungee.logger().log(Level.WARNING, "[Pre][" + ctx.channel().remoteAddress() + "]: Exception Caught: " + cause.toString(), cause);
ctx.close();
}
}
private static String formatAddressFor404(String str) {
return "<span style=\"font-family:monospace;font-weight:bold;background-color:#EEEEEE;padding:3px 4px;\">" + str.replace("<", "&lt;").replace(">", "&gt;") + "</span>";
}
public void channelInactive(ChannelHandlerContext ctx) {
EaglerPipeline.closeChannel(ctx.channel());
}
}

View File

@ -0,0 +1,232 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query.QueryManager;
/**
* 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 abstract class HttpServerQueryHandler extends ChannelInboundHandlerAdapter {
public static class UnexpectedDataException extends RuntimeException {
public UnexpectedDataException() {
}
public UnexpectedDataException(String message, Throwable cause) {
super(message, cause);
}
public UnexpectedDataException(String message) {
super(message);
}
public UnexpectedDataException(Throwable cause) {
super(cause);
}
}
private static final InetAddress localhost;
static {
try {
localhost = InetAddress.getLocalHost();
}catch(Throwable t) {
throw new RuntimeException("localhost doesn't exist?!", t);
}
}
private EaglerListenerConfig conf;
private ChannelHandlerContext context;
private String accept;
private boolean acceptTextPacket = false;
private boolean acceptBinaryPacket = false;
private boolean hasClosed = false;
private boolean keepAlive = false;
public void beginHandleQuery(EaglerListenerConfig conf, ChannelHandlerContext context, String accept) {
this.conf = conf;
this.context = context;
this.accept = accept;
begin(accept);
}
protected void acceptText() {
acceptText(true);
}
protected void acceptText(boolean bool) {
acceptTextPacket = bool;
}
protected void acceptBinary() {
acceptBinary(true);
}
protected void acceptBinary(boolean bool) {
acceptBinaryPacket = bool;
}
public void close() {
context.close();
hasClosed = true;
}
public boolean isClosed() {
return hasClosed;
}
public InetAddress getAddress() {
InetAddress addr = context.channel().attr(EaglerPipeline.REAL_ADDRESS).get();
if(addr != null) {
return addr;
}else {
SocketAddress sockAddr = context.channel().remoteAddress();
return sockAddr instanceof InetSocketAddress ? ((InetSocketAddress) sockAddr).getAddress() : localhost;
}
}
public ChannelHandlerContext getContext() {
return context;
}
public EaglerListenerConfig getListener() {
return conf;
}
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof WebSocketFrame) {
if(msg instanceof BinaryWebSocketFrame) {
handleBinary(ctx, ((BinaryWebSocketFrame)msg).content());
}else if(msg instanceof TextWebSocketFrame) {
handleText(ctx, ((TextWebSocketFrame)msg).text());
}else if(msg instanceof PingWebSocketFrame) {
ctx.writeAndFlush(new PongWebSocketFrame());
}else if(msg instanceof CloseWebSocketFrame) {
ctx.close();
}
}else {
EaglerXBungee.logger().severe("Unexpected Packet: " + msg.getClass().getSimpleName());
}
}
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (ctx.channel().isActive()) {
EaglerXBungee.logger().warning("[" + ctx.channel().remoteAddress() + "]: Exception Caught: " + cause.toString());
}
}
private void handleBinary(ChannelHandlerContext ctx, ByteBuf buffer) {
if(!acceptBinaryPacket) {
ctx.close();
return;
}
byte[] packet = new byte[buffer.readableBytes()];
buffer.readBytes(packet);
processBytes(packet);
}
private void handleText(ChannelHandlerContext ctx, String str) {
if(!acceptTextPacket) {
ctx.close();
return;
}
JsonObject obj = null;
if(str.indexOf('{') == 0) {
try {
obj = (new JsonParser()).parse(str).getAsJsonObject();
}catch(JsonParseException ex) {
}
}
if(obj != null) {
processJson(obj);
}else {
processString(str);
}
}
public void channelInactive(ChannelHandlerContext ctx) {
EaglerPipeline.closeChannel(ctx.channel());
hasClosed = true;
closed();
}
public String getAccept() {
return accept;
}
public void sendStringResponse(String type, String str) {
context.writeAndFlush(new TextWebSocketFrame(QueryManager.createStringResponse(accept, str).toString()));
}
public void sendStringResponseAndClose(String type, String str) {
context.writeAndFlush(new TextWebSocketFrame(QueryManager.createStringResponse(accept, str).toString())).addListener(ChannelFutureListener.CLOSE);
}
public void sendJsonResponse(String type, JsonObject obj) {
context.writeAndFlush(new TextWebSocketFrame(QueryManager.createJsonObjectResponse(accept, obj).toString()));
}
public void sendJsonResponseAndClose(String type, JsonObject obj) {
context.writeAndFlush(new TextWebSocketFrame(QueryManager.createJsonObjectResponse(accept, obj).toString())).addListener(ChannelFutureListener.CLOSE);
}
public void sendBinaryResponse(byte[] bytes) {
ByteBuf buf = Unpooled.buffer(bytes.length, bytes.length);
buf.writeBytes(bytes);
context.writeAndFlush(new BinaryWebSocketFrame(buf));
}
public void sendBinaryResponseAndClose(byte[] bytes) {
ByteBuf buf = Unpooled.buffer(bytes.length, bytes.length);
buf.writeBytes(bytes);
context.writeAndFlush(new BinaryWebSocketFrame(buf)).addListener(ChannelFutureListener.CLOSE);
}
public void setKeepAlive(boolean enable) {
keepAlive = enable;
}
public boolean shouldKeepAlive() {
return keepAlive;
}
protected abstract void begin(String queryType);
protected abstract void processString(String str);
protected abstract void processJson(JsonObject obj);
protected abstract void processBytes(byte[] bytes);
protected abstract void closed();
}

View File

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

View File

@ -0,0 +1,32 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.bungeeprotocol;
import net.md_5.bungee.protocol.DefinedPacket;
/**
* 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 EaglerProtocolAccessProxy {
public static int getPacketId(EaglerBungeeProtocol protocol, int protocolVersion, DefinedPacket pkt, boolean server) {
final EaglerBungeeProtocol.DirectionData prot = server ? protocol.TO_CLIENT : protocol.TO_SERVER;
return prot.getId((Class) pkt.getClass(), protocolVersion);
}
public static DefinedPacket createPacket(EaglerBungeeProtocol protocol, int protocolVersion, int packetId, boolean server) {
final EaglerBungeeProtocol.DirectionData prot = server ? protocol.TO_CLIENT : protocol.TO_SERVER;
return prot.createPacket(packetId, protocolVersion);
}
}

View File

@ -0,0 +1,228 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.EaglerQuerySimpleHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.MOTDConnection;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.MOTDCacheConfiguration;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* 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 MOTDQueryHandler extends EaglerQuerySimpleHandler implements MOTDConnection {
private long creationTime = 0l;
private String line1;
private String line2;
private List<String> players;
private int[] bitmap;
private int onlinePlayers;
private int maxPlayers;
private boolean hasIcon;
private boolean iconDirty;
private String subType;
private String returnType;
@Override
protected void begin(String queryType) {
creationTime = System.currentTimeMillis();
subType = queryType;
returnType = "MOTD";
EaglerListenerConfig listener = getListener();
String[] lns = listener.getMotd().split("\n");
if(lns.length >= 1) {
line1 = lns[0];
}
if(lns.length >= 2) {
line2 = lns[1];
}
maxPlayers = listener.getMaxPlayers();
onlinePlayers = ProxyServer.getInstance().getOnlineCount();
players = new ArrayList();
for(ProxiedPlayer pp : ProxyServer.getInstance().getPlayers()) {
players.add(pp.getDisplayName());
if(players.size() >= 9) {
players.add("" + ChatColor.GRAY + ChatColor.ITALIC + "(" + (onlinePlayers - players.size()) + " more)");
break;
}
}
bitmap = new int[4096];
int i = queryType.indexOf('.');
if(i > 0) {
subType = queryType.substring(i + 1);
if(subType.length() == 0) {
subType = "motd";
}
}else {
subType = "motd";
}
if(!subType.startsWith("noicon") && !subType.startsWith("cache.noicon")) {
int[] maybeIcon = listener.getServerIconPixels();
iconDirty = hasIcon = maybeIcon != null;
if(hasIcon) {
System.arraycopy(maybeIcon, 0, bitmap, 0, 4096);
}
}
}
@Override
public long getConnectionTimestamp() {
return creationTime;
}
@Override
public void sendToUser() {
if(!isClosed()) {
JsonObject obj = new JsonObject();
if(subType.startsWith("cache.anim")) {
obj.addProperty("unsupported", true);
sendJsonResponseAndClose(returnType, obj);
return;
}else if(subType.startsWith("cache")) {
JsonArray cacheControl = new JsonArray();
MOTDCacheConfiguration cc = getListener().getMOTDCacheConfig();
if(cc.cacheServerListAnimation) {
cacheControl.add("animation");
}
if(cc.cacheServerListResults) {
cacheControl.add("results");
}
if(cc.cacheServerListTrending) {
cacheControl.add("trending");
}
if(cc.cacheServerListPortfolios) {
cacheControl.add("portfolio");
}
obj.add("cache", cacheControl);
obj.addProperty("ttl", cc.cacheTTL);
}else {
MOTDCacheConfiguration cc = getListener().getMOTDCacheConfig();;
obj.addProperty("cache", cc.cacheServerListAnimation || cc.cacheServerListResults ||
cc.cacheServerListTrending || cc.cacheServerListPortfolios);
}
boolean noIcon = subType.startsWith("noicon") || subType.startsWith("cache.noicon");
JsonArray motd = new JsonArray();
if(line1 != null && line1.length() > 0) motd.add(line1);
if(line2 != null && line2.length() > 0) motd.add(line2);
obj.add("motd", motd);
obj.addProperty("icon", hasIcon && !noIcon);
obj.addProperty("online", onlinePlayers);
obj.addProperty("max", maxPlayers);
JsonArray playerz = new JsonArray();
for(String s : players) {
playerz.add(s);
}
obj.add("players", playerz);
sendJsonResponse(returnType, obj);
if(hasIcon && !noIcon && iconDirty && bitmap != null) {
byte[] iconPixels = new byte[16384];
for(int i = 0, j; i < 4096; ++i) {
j = i << 2;
iconPixels[j] = (byte)((bitmap[i] >> 16) & 0xFF);
iconPixels[j + 1] = (byte)((bitmap[i] >> 8) & 0xFF);
iconPixels[j + 2] = (byte)(bitmap[i] & 0xFF);
iconPixels[j + 3] = (byte)((bitmap[i] >> 24) & 0xFF);
}
sendBinaryResponse(iconPixels);
iconDirty = false;
}
if(subType.startsWith("cache")) {
close();
}
}
}
@Override
public String getLine1() {
return line1;
}
@Override
public String getLine2() {
return line2;
}
@Override
public List<String> getPlayerList() {
return players;
}
@Override
public int[] getBitmap() {
return bitmap;
}
@Override
public int getOnlinePlayers() {
return onlinePlayers;
}
@Override
public int getMaxPlayers() {
return maxPlayers;
}
@Override
public String getSubType() {
return subType;
}
@Override
public void setLine1(String p) {
line1 = p;
}
@Override
public void setLine2(String p) {
line2 = p;
}
@Override
public void setPlayerList(List<String> p) {
players = p;
}
@Override
public void setPlayerList(String... p) {
players = Arrays.asList(p);
}
@Override
public void setBitmap(int[] p) {
iconDirty = hasIcon = true;
bitmap = p;
}
@Override
public void setOnlinePlayers(int i) {
onlinePlayers = i;
}
@Override
public void setMaxPlayers(int i) {
maxPlayers = i;
}
}

View File

@ -0,0 +1,102 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import com.google.gson.JsonObject;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.HttpServerQueryHandler;
import net.md_5.bungee.api.plugin.PluginDescription;
/**
* 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 QueryManager {
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);
}
public static HttpServerQueryHandler createQueryHandler(String type) {
Class<? extends HttpServerQueryHandler> clazz;
synchronized(queryTypes) {
clazz = queryTypes.get(type);
}
if(clazz != null) {
HttpServerQueryHandler obj = null;
try {
obj = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception creating query handler for '" + type + "'!", e);
}
if(obj != null) {
return obj;
}
}
return null;
}
public static void registerQueryType(String name, Class<? extends HttpServerQueryHandler> clazz) {
synchronized(queryTypes) {
if(queryTypes.put(name, clazz) != null) {
EaglerXBungee.logger().warning("Query type '" + name + "' was registered twice, probably by two different plugins!");
Thread.dumpStack();
}
}
}
public static void unregisterQueryType(String name) {
synchronized(queryTypes) {
queryTypes.remove(name);
}
}
private static JsonObject createBaseResponse() {
EaglerXBungee plugin = EaglerXBungee.getEagler();
EaglerBungeeConfig conf = plugin.getConfig();
JsonObject json = new JsonObject();
json.addProperty("name", conf.getServerName());
json.addProperty("brand", "lax1dude");
PluginDescription desc = plugin.getDescription();
json.addProperty("vers", "EaglerXBungee/" + desc.getVersion());
json.addProperty("cracked", conf.isCracked());
json.addProperty("secure", false);
json.addProperty("time", System.currentTimeMillis());
json.addProperty("uuid", conf.getServerUUID().toString());
return json;
}
public static JsonObject createStringResponse(String type, String str) {
JsonObject ret = createBaseResponse();
ret.addProperty("type", type);
ret.addProperty("data", str);
return ret;
}
public static JsonObject createJsonObjectResponse(String type, JsonObject json) {
JsonObject ret = createBaseResponse();
ret.addProperty("type", type);
ret.add("data", json);
return ret;
}
}

View File

@ -0,0 +1,53 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.query;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.EaglerQuerySimpleHandler;
import net.md_5.bungee.api.ProxyServer;
/**
* 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 VersionQueryHandler extends EaglerQuerySimpleHandler {
@Override
protected void begin(String queryType) {
JsonObject responseObj = new JsonObject();
JsonArray handshakeVersions = new JsonArray();
handshakeVersions.add(2);
handshakeVersions.add(3);
responseObj.add("handshakeVersions", handshakeVersions);
JsonArray protocolVersions = new JsonArray();
protocolVersions.add(47);
responseObj.add("protocolVersions", protocolVersions);
JsonArray gameVersions = new JsonArray();
gameVersions.add("1.8");
responseObj.add("gameVersions", gameVersions);
JsonObject proxyInfo = new JsonObject();
proxyInfo.addProperty("brand", ProxyServer.getInstance().getName());
proxyInfo.addProperty("vers", ProxyServer.getInstance().getVersion());
responseObj.add("proxyVersions", proxyInfo);
sendJsonResponseAndClose("version", responseObj);
}
}

View File

@ -0,0 +1,49 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web;
import java.util.HashSet;
import java.util.Set;
/**
* 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 HttpContentType {
public final Set<String> extensions;
public final String mimeType;
public final String charset;
public final String httpHeader;
public final String cacheControlHeader;
public final long fileBrowserCacheTTL;
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;
this.mimeType = mimeType;
this.charset = charset;
this.fileBrowserCacheTTL = fileBrowserCacheTTL;
if(charset == null) {
this.httpHeader = mimeType;
}else {
this.httpHeader = mimeType + "; charset=" + charset;
}
if(fileBrowserCacheTTL > 0l) {
this.cacheControlHeader = "max-age=" + (fileBrowserCacheTTL / 1000l);
}else {
this.cacheControlHeader = "no-cache";
}
}
}

View File

@ -0,0 +1,86 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.SimpleTimeZone;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
/**
* 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 HttpMemoryCache {
public File fileObject;
public String filePath;
public ByteBuf fileData;
public HttpContentType contentType;
public long lastCacheHit;
public long lastDiskReload;
public long lastDiskModified;
private final String server;
private static final SimpleDateFormat gmt;
static {
gmt = new SimpleDateFormat();
gmt.setTimeZone(new SimpleTimeZone(0, "GMT"));
gmt.applyPattern("dd MMM yyyy HH:mm:ss z");
}
public HttpMemoryCache(File fileObject, String filePath, ByteBuf fileData, HttpContentType contentType,
long lastCacheHit, long lastDiskReload, long lastDiskModified) {
this.fileObject = fileObject;
this.filePath = filePath;
this.fileData = fileData;
this.contentType = contentType;
this.lastCacheHit = lastCacheHit;
this.lastDiskReload = lastDiskReload;
this.lastDiskModified = lastDiskModified;
this.server = "EaglerXBungee/" + EaglerXBungee.getEagler().getDescription().getVersion();
}
public DefaultFullHttpResponse createHTTPResponse() {
return createHTTPResponse(HttpResponseStatus.OK);
}
public DefaultFullHttpResponse createHTTPResponse(HttpResponseStatus code) {
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, code, Unpooled.copiedBuffer(fileData));
HttpHeaders responseHeaders = response.headers();
Date d = new Date();
responseHeaders.add(HttpHeaderNames.CONTENT_TYPE, contentType.httpHeader);
responseHeaders.add(HttpHeaderNames.CONTENT_LENGTH, fileData.readableBytes());
responseHeaders.add(HttpHeaderNames.CACHE_CONTROL, contentType.cacheControlHeader);
responseHeaders.add(HttpHeaderNames.DATE, gmt.format(d));
long l = contentType.fileBrowserCacheTTL;
if(l > 0l && l != Long.MAX_VALUE) {
d.setTime(d.getTime() + l);
responseHeaders.add(HttpHeaderNames.EXPIRES, gmt.format(d));
}
d.setTime(lastDiskModified);
responseHeaders.add(HttpHeaderNames.LAST_MODIFIED, gmt.format(d));
responseHeaders.add(HttpHeaderNames.SERVER, server);
return response;
}
}

View File

@ -0,0 +1,291 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.web;
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 io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
/**
* 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 HttpWebServer {
public final File directory;
public final Map<String,HttpContentType> contentTypes;
private final Map<String,HttpMemoryCache> filesCache;
private final List<String> index;
private final String page404;
private static HttpMemoryCache default404Page;
private static HttpMemoryCache default404UpgradePage;
private static final Object cacheClearLock = new Object();
public HttpWebServer(File directory, Map<String,HttpContentType> contentTypes, List<String> index, String page404) {
this.directory = directory;
this.contentTypes = contentTypes;
this.filesCache = new HashMap();
this.index = index;
this.page404 = page404;
}
public void flushCache() {
long millis = System.currentTimeMillis();
synchronized(cacheClearLock) {
synchronized(filesCache) {
Iterator<HttpMemoryCache> itr = filesCache.values().iterator();
while(itr.hasNext()) {
HttpMemoryCache i = itr.next();
if(i.contentType.fileBrowserCacheTTL != Long.MAX_VALUE && millis - i.lastCacheHit > 900000l) {
i.fileData.release();
itr.remove();
}
}
}
}
}
public HttpMemoryCache retrieveFile(String path) {
try {
String[] pathSplit = path.split("(\\\\|\\/)+");
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) {
if(!pathSplit[i].equals(".") && !pathSplit[i].startsWith("..")) {
pathList.add(pathSplit[i]);
}
}
}
HttpMemoryCache cached;
if(pathList == null || pathList.size() == 0) {
for(int i = 0, l = index.size(); i < l; ++i) {
cached = retrieveFile(index.get(i));
if(cached != null) {
return cached;
}
}
return null;
}
String joinedPath = String.join("/", pathList);
synchronized(cacheClearLock) {
synchronized(filesCache) {
cached = filesCache.get(joinedPath);
}
if(cached != null) {
cached = validateCache(cached);
if(cached != null) {
return cached;
}else {
synchronized(filesCache) {
filesCache.remove(joinedPath);
}
}
}
File f = new File(directory, joinedPath);
if(!f.exists()) {
if(page404 == null || path.equals(page404)) {
return default404Page;
}else {
return retrieveFile(page404);
}
}
if(f.isDirectory()) {
for(int i = 0, l = index.size(); i < l; ++i) {
String p = joinedPath + "/" + index.get(i);
synchronized(filesCache) {
cached = filesCache.get(p);
}
if(cached != null) {
cached = validateCache(cached);
if(cached != null) {
synchronized(filesCache) {
filesCache.put(joinedPath, cached);
}
}else {
synchronized(filesCache) {
filesCache.remove(p);
}
if(page404 == null || path.equals(page404)) {
return default404Page;
}else {
return retrieveFile(page404);
}
}
return cached;
}
}
for(int i = 0, l = index.size(); i < l; ++i) {
String p = joinedPath + "/" + index.get(i);
File ff = new File(directory, p);
if(ff.isFile()) {
HttpMemoryCache memCache = retrieveFile(ff, p);
if(memCache != null) {
synchronized(filesCache) {
filesCache.put(joinedPath, memCache);
}
return memCache;
}
}
}
if(page404 == null || path.equals(page404)) {
return default404Page;
}else {
return retrieveFile(page404);
}
}else {
HttpMemoryCache memCache = retrieveFile(f, joinedPath);
if(memCache != null) {
synchronized(filesCache) {
filesCache.put(joinedPath, memCache);
}
return memCache;
}else {
if(page404 == null || path.equals(page404)) {
return default404Page;
}else {
return retrieveFile(page404);
}
}
}
}
}catch(Throwable t) {
return default404Page;
}
}
private HttpMemoryCache retrieveFile(File path, String requestCachePath) {
int fileSize = (int)path.length();
try(FileInputStream is = new FileInputStream(path)) {
ByteBuf file = Unpooled.buffer(fileSize, fileSize);
file.writeBytes(is, fileSize);
String ext = path.getName();
HttpContentType ct = null;
int i = ext.lastIndexOf('.');
if(i != -1) {
ct = contentTypes.get(ext.substring(i + 1));
}
if(ct == null) {
ct = HttpContentType.defaultType;
}
long millis = System.currentTimeMillis();
return new HttpMemoryCache(path, requestCachePath, file, ct, millis, millis, path.lastModified());
}catch(Throwable t) {
return null;
}
}
private HttpMemoryCache validateCache(HttpMemoryCache file) {
if(file.fileObject == null) {
return file;
}
long millis = System.currentTimeMillis();
file.lastCacheHit = millis;
if(millis - file.lastDiskReload > 4000l) {
File f = file.fileObject;
if(!f.isFile()) {
return null;
}else {
long lastMod = f.lastModified();
if(lastMod != file.lastDiskModified) {
int fileSize = (int)f.length();
try(FileInputStream is = new FileInputStream(f)) {
file.fileData = Unpooled.buffer(fileSize, fileSize);
file.fileData.writeBytes(is, fileSize);
file.lastDiskReload = millis;
file.lastDiskModified = lastMod;
return file;
}catch(Throwable t) {
return null;
}
}else {
return file;
}
}
}else {
return file;
}
}
public static void regenerate404Pages() {
if(default404Page != null) {
default404Page.fileData.release();
}
default404Page = regenerateDefault404();
if(default404UpgradePage != null) {
default404UpgradePage.fileData.release();
}
default404UpgradePage = regenerateDefaultUpgrade404();
}
public static HttpMemoryCache getHTTP404() {
return default404Page;
}
public static HttpMemoryCache getWebSocket404() {
return default404UpgradePage;
}
private static HttpMemoryCache regenerateDefault404() {
EaglerXBungee plugin = EaglerXBungee.getEagler();
byte[] src = ("<!DOCTYPE html><html><head><title>" + htmlEntities(plugin.getConfig().getServerName()) + "</title><script type=\"text/javascript\">"
+ "window.addEventListener(\"load\",()=>document.getElementById(\"addr\").innerText=window.location.href);</script></head>"
+ "<body style=\"font-family:sans-serif;text-align:center;\"><h1>404 Not Found</h1><hr /><p style=\"font-size:1.2em;\">"
+ "The requested resource <span id=\"addr\" style=\"font-family:monospace;font-weight:bold;background-color:#EEEEEE;padding:3px 4px;\">"
+ "</span> could not be found on this server!</p><p>" + htmlEntities(plugin.getDescription().getName()) + "/"
+ htmlEntities(plugin.getDescription().getVersion()) + "</p></body></html>").getBytes(StandardCharsets.UTF_8);
HttpContentType htmlContentType = new HttpContentType(new HashSet(Arrays.asList("html")), "text/html", "utf-8", 120000l);
long millis = System.currentTimeMillis();
return new HttpMemoryCache(null, "~404", Unpooled.wrappedBuffer(src), htmlContentType, millis, millis, millis);
}
private static HttpMemoryCache regenerateDefaultUpgrade404() {
EaglerXBungee plugin = EaglerXBungee.getEagler();
String name = htmlEntities(plugin.getConfig().getServerName());
byte[] src = ("<!DOCTYPE html><html><head><meta charset=\"UTF-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /><title>" + name +
"</title><script type=\"text/javascript\">window.addEventListener(\"load\",()=>{var src=window.location.href;const gEI=(i)=>document.getElementById(i);"
+ "if(src.startsWith(\"http:\")){src=\"ws:\"+src.substring(5);}else if(src.startsWith(\"https:\")){src=\"wss:\"+src.substring(6);}else{return;}"
+ "gEI(\"wsUri\").innerHTML=\"<span id=\\\"wsField\\\" style=\\\"font-family:monospace;font-weight:bold;background-color:#EEEEEE;padding:3px 4px;\\\">"
+ "</span>\";gEI(\"wsField\").innerText=src;});</script></head><body style=\"font-family:sans-serif;margin:0px;padding:12px;\"><h1 style=\"margin-block-start:0px;\">"
+ "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();
return new HttpMemoryCache(null, "~404", Unpooled.wrappedBuffer(src), htmlContentType, millis, millis, millis);
}
public static String htmlEntities(String input) {
return input.replace("<", "&lt;").replace(">", "&gt;");
}
}

View File

@ -0,0 +1,61 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.shit;
import java.util.logging.Logger;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.md_5.bungee.api.ProxyServer;
/**
* 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 CompatWarning {
public static void displayCompatWarning() {
String stfu = System.getProperty("eaglerxbungee.stfu");
if("true".equalsIgnoreCase(stfu)) {
return;
}
String[] compatWarnings = new String[] {
":>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>",
":> ",
":> EAGLERCRAFTXBUNGEE WARNING:",
":> ",
":> This plugin wasn\'t tested to be \'working\'",
":> with ANY version of BungeeCord (and forks)",
":> apart from the versions listed below:",
":> ",
":> - BungeeCord: " + EaglerXBungee.NATIVE_BUNGEECORD_BUILD,
":> - Waterfall: " + EaglerXBungee.NATIVE_WATERFALL_BUILD,
":> ",
":> This is not a Bukkit/Spigot plugin!",
":> ",
":> Use \"-Deaglerxbungee.stfu=true\" to hide",
":> ",
":>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>"
};
try {
Logger fuck = ProxyServer.getInstance().getLogger();
for(int i = 0; i < compatWarnings.length; ++i) {
fuck.warning(compatWarnings[i]);
}
}catch(Throwable t) {
for(int i = 0; i < compatWarnings.length; ++i) {
System.err.println(compatWarnings[i]);
}
}
}
}

View File

@ -0,0 +1,41 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.shit;
import java.awt.GraphicsEnvironment;
import javax.swing.JOptionPane;
/**
* 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 MainClass {
public static void main(String[] args) {
System.err.println();
System.err.println("ERROR: The EaglerXBungee 1.8 jar file is a PLUGIN intended to be used with BungeeCord!");
System.err.println("Place this file in the \"plugins\" directory of your BungeeCord installation");
System.err.println();
try {
tryShowPopup();
}catch(Throwable t) {
}
System.exit(0);
}
private static void tryShowPopup() throws Throwable {
if(!GraphicsEnvironment.isHeadless()) {
JOptionPane.showMessageDialog(null, "ERROR: The EaglerXBungee 1.8 jar file is a PLUGIN intended to be used with BungeeCord!\nPlace this file in the \"plugins\" directory of your BungeeCord installation", "EaglerXBungee", JOptionPane.ERROR_MESSAGE);
}
}
}

View File

@ -0,0 +1,455 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.logging.Level;
import javax.imageio.ImageIO;
import org.apache.commons.codec.binary.Base64;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.BinaryHttpClient.Response;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.ICacheProvider.CacheLoadedProfile;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins.ICacheProvider.CacheLoadedSkin;
/**
* 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 AsyncSkinProvider {
private static class SkinConsumerImpl implements Consumer<Response> {
protected final Consumer<byte[]> responseConsumer;
protected SkinConsumerImpl(Consumer<byte[]> consumer) {
this.responseConsumer = consumer;
}
protected void doAccept(byte[] v) {
try {
responseConsumer.accept(v);
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception thrown caching new skin!", t);
}
}
@Override
public void accept(Response response) {
if(response == null || response.exception != null || response.code != 200 || response.data == null) {
doAccept(null);
}else {
BufferedImage image;
try {
image = ImageIO.read(new ByteArrayInputStream(response.data));
}catch(IOException ex) {
doAccept(null);
return;
}
try {
int srcWidth = image.getWidth();
int srcHeight = image.getHeight();
if(srcWidth < 64 || srcWidth > 512 || srcHeight < 32 || srcHeight > 512) {
doAccept(null);
return;
}
if(srcWidth != 64 || srcHeight != 64) {
if(srcWidth % 64 == 0) {
if(srcWidth == srcHeight * 2) {
BufferedImage scaled = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = scaled.createGraphics();
graphics.drawImage(image, 0, 0, 64, 32, 0, 0, srcWidth, srcHeight, null);
graphics.dispose();
image = scaled;
srcWidth = 64;
srcHeight = 32;
}else if(srcWidth == srcHeight) {
BufferedImage scaled = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = scaled.createGraphics();
graphics.drawImage(image, 0, 0, 64, 64, 0, 0, srcWidth, srcHeight, null);
graphics.dispose();
image = scaled;
srcWidth = 64;
srcHeight = 64;
}else {
doAccept(null);
return;
}
}else {
doAccept(null);
return;
}
}
if(srcWidth == 64 && srcHeight == 64) {
int[] tmp = new int[4096];
byte[] loadedPixels = new byte[16384];
image.getRGB(0, 0, 64, 64, tmp, 0, 64);
SkinRescaler.convertToBytes(tmp, loadedPixels);
SkinPackets.setAlphaForChest(loadedPixels, (byte)255, 0);
doAccept(loadedPixels);
return;
}else if(srcWidth == 64 && srcHeight == 32) {
int[] tmp1 = new int[2048];
byte[] loadedPixels = new byte[16384];
image.getRGB(0, 0, 64, 32, tmp1, 0, 64);
SkinRescaler.convert64x32To64x64(tmp1, loadedPixels);
SkinPackets.setAlphaForChest(loadedPixels, (byte)255, 0);
doAccept(loadedPixels);
return;
}else {
doAccept(null);
return;
}
}catch(Throwable t) {
}
}
}
}
private static class SkinCachingConsumer implements Consumer<byte[]> {
protected final UUID skinUUID;
protected final String skinTexture;
protected final ICacheProvider cacheProvider;
protected final Consumer<byte[]> responseConsumer;
protected SkinCachingConsumer(UUID skinUUID, String skinTexture, ICacheProvider cacheProvider,
Consumer<byte[]> responseConsumer) {
this.skinUUID = skinUUID;
this.skinTexture = skinTexture;
this.cacheProvider = cacheProvider;
this.responseConsumer = responseConsumer;
}
@Override
public void accept(byte[] skin) {
if(skin != null) {
try {
cacheProvider.cacheSkinByUUID(skinUUID, skinTexture, skin);
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception thrown writing new skin to database!", t);
}
responseConsumer.accept(skin);
}else {
responseConsumer.accept(null);
}
}
}
public static class CacheFetchedProfile {
public final UUID uuid;
public final String username;
public final String texture;
public final UUID textureUUID;
public final String model;
protected CacheFetchedProfile(UUID uuid, String username, String texture, String model) {
this.uuid = uuid;
this.username = username;
this.texture = texture;
this.textureUUID = SkinPackets.createEaglerURLSkinUUID(texture);
this.model = model;
}
protected CacheFetchedProfile(CacheLoadedProfile profile) {
this.uuid = profile.uuid;
this.username = profile.username;
this.texture = profile.texture;
this.textureUUID = SkinPackets.createEaglerURLSkinUUID(profile.texture);
this.model = profile.model;
}
}
private static class ProfileConsumerImpl implements Consumer<Response> {
protected final UUID uuid;
protected final Consumer<CacheFetchedProfile> responseConsumer;
protected ProfileConsumerImpl(UUID uuid, Consumer<CacheFetchedProfile> responseConsumer) {
this.uuid = uuid;
this.responseConsumer = responseConsumer;
}
protected void doAccept(CacheFetchedProfile v) {
try {
responseConsumer.accept(v);
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception thrown caching new profile!", t);
}
}
@Override
public void accept(Response response) {
if(response == null || response.exception != null || response.code != 200 || response.data == null) {
doAccept(null);
}else {
try {
JsonObject json = (new JsonParser()).parse(new String(response.data, StandardCharsets.UTF_8)).getAsJsonObject();
String username = json.get("name").getAsString().toLowerCase();
String texture = null;
String model = null;
JsonElement propsElement = json.get("properties");
if(propsElement != null) {
try {
JsonArray properties = propsElement.getAsJsonArray();
if(properties.size() > 0) {
for(int i = 0, l = properties.size(); i < l; ++i) {
JsonElement prop = properties.get(i);
if(prop.isJsonObject()) {
JsonObject propObj = prop.getAsJsonObject();
if(propObj.get("name").getAsString().equals("textures")) {
String value = new String(Base64.decodeBase64(propObj.get("value").getAsString()), StandardCharsets.UTF_8);
JsonObject texturesJson = (new JsonParser()).parse(value).getAsJsonObject();
if(texturesJson != null && texturesJson.has("textures")) {
texturesJson = texturesJson.getAsJsonObject("textures");
JsonElement skin = texturesJson.get("SKIN");
if(skin != null) {
model = "default";
JsonObject skinObj = skin.getAsJsonObject();
JsonElement urlElement = skinObj.get("url");
if(urlElement != null && !urlElement.isJsonNull()) {
texture = urlElement.getAsString();
}
JsonElement metaElement = skinObj.get("metadata");
if(metaElement != null) {
JsonObject metaObj = metaElement.getAsJsonObject();
JsonElement modelElement = metaObj.get("model");
if(modelElement != null) {
model = modelElement.getAsString();
}
}
}
}
break;
}
}
}
}
}catch(Throwable t2) {
}
}
if(texture == null && model == null) {
model = SkinService.isAlex(uuid) ? "slim" : "default";
}
doAccept(new CacheFetchedProfile(uuid, username, texture, model));
}catch(Throwable ex) {
doAccept(null);
}
}
}
}
private static class ProfileCachingConsumer implements Consumer<CacheFetchedProfile> {
protected final UUID uuid;
protected final ICacheProvider cacheProvider;
protected final Consumer<CacheFetchedProfile> responseConsumer;
protected ProfileCachingConsumer(UUID uuid, ICacheProvider cacheProvider, Consumer<CacheFetchedProfile> responseConsumer) {
this.uuid = uuid;
this.cacheProvider = cacheProvider;
this.responseConsumer = responseConsumer;
}
@Override
public void accept(CacheFetchedProfile profile) {
if(profile != null) {
try {
cacheProvider.cacheProfileByUUID(uuid, profile.username, profile.texture, profile.model);
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception thrown writing new profile to database!", t);
}
responseConsumer.accept(profile);
}else {
responseConsumer.accept(null);
}
}
}
private static class UsernameToUUIDConsumerImpl implements Consumer<Response> {
protected final String username;
protected final ICacheProvider cacheProvider;
protected final Consumer<CacheFetchedProfile> responseConsumer;
protected UsernameToUUIDConsumerImpl(String username, ICacheProvider cacheProvider, Consumer<CacheFetchedProfile> responseConsumer) {
this.username = username;
this.cacheProvider = cacheProvider;
this.responseConsumer = responseConsumer;
}
protected void doAccept(CacheFetchedProfile v) {
try {
responseConsumer.accept(v);
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception thrown caching new profile!", t);
}
}
@Override
public void accept(Response response) {
if(response == null || response.exception != null || response.code != 200 || response.data == null) {
doAccept(null);
}else {
try {
JsonObject json = (new JsonParser()).parse(new String(response.data, StandardCharsets.UTF_8)).getAsJsonObject();
String loadUsername = json.get("name").getAsString().toLowerCase();
if(!username.equals(loadUsername)) {
doAccept(null);
}
UUID mojangUUID = SkinService.parseMojangUUID(json.get("id").getAsString());
lookupProfileByUUID(mojangUUID, cacheProvider, responseConsumer, false);
}catch(Throwable t) {
doAccept(null);
}
}
}
}
private static final SimpleRateLimiter rateLimitDownload = new SimpleRateLimiter();
private static final SimpleRateLimiter rateLimitLookup = new SimpleRateLimiter();
public static void downloadSkin(String skinTexture, ICacheProvider cacheProvider, Consumer<byte[]> responseConsumer) {
downloadSkin(SkinPackets.createEaglerURLSkinUUID(skinTexture), skinTexture, cacheProvider, responseConsumer);
}
public static void downloadSkin(UUID skinUUID, String skinTexture, ICacheProvider cacheProvider, Consumer<byte[]> responseConsumer) {
CacheLoadedSkin loadedSkin = cacheProvider.loadSkinByUUID(skinUUID);
if(loadedSkin == null) {
URI uri;
try {
uri = URI.create(skinTexture);
}catch(IllegalArgumentException ex) {
try {
responseConsumer.accept(null);
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception thrown handling invalid skin!", t);
}
throw new CancelException();
}
int globalRatelimit = EaglerXBungee.getEagler().getConfig().getSkinRateLimitGlobal();
boolean isRateLimit;
synchronized(rateLimitDownload) {
isRateLimit = !rateLimitDownload.rateLimit(globalRatelimit);
}
if(!isRateLimit) {
BinaryHttpClient.asyncRequest("GET", uri, new SkinConsumerImpl(
new SkinCachingConsumer(skinUUID, skinTexture, cacheProvider, responseConsumer)));
}else {
EaglerXBungee.logger().warning("skin system reached the global texture download ratelimit of " + globalRatelimit + " while downloading up \"" + skinTexture + "\"");
throw new CancelException();
}
}else {
try {
responseConsumer.accept(loadedSkin.texture);
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception thrown processing cached skin!", t);
}
throw new CancelException();
}
}
public static void lookupProfileByUUID(UUID playerUUID, ICacheProvider cacheProvider, Consumer<CacheFetchedProfile> responseConsumer) {
lookupProfileByUUID(playerUUID, cacheProvider, responseConsumer, true);
}
private static void lookupProfileByUUID(UUID playerUUID, ICacheProvider cacheProvider, Consumer<CacheFetchedProfile> responseConsumer, boolean rateLimit) {
CacheLoadedProfile profile = cacheProvider.loadProfileByUUID(playerUUID);
if(profile == null) {
URI requestURI = URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + SkinService.getMojangUUID(playerUUID));
int globalRatelimit = EaglerXBungee.getEagler().getConfig().getUuidRateLimitGlobal();
boolean isRateLimit;
if(rateLimit) {
synchronized(rateLimitLookup) {
isRateLimit = !rateLimitLookup.rateLimit(globalRatelimit);
}
}else {
isRateLimit = false;
}
if(!isRateLimit) {
BinaryHttpClient.asyncRequest("GET", requestURI, new ProfileConsumerImpl(playerUUID,
new ProfileCachingConsumer(playerUUID, cacheProvider, responseConsumer)));
}else {
EaglerXBungee.logger().warning("skin system reached the global UUID lookup ratelimit of " + globalRatelimit + " while looking up \"" + playerUUID + "\"");
throw new CancelException();
}
}else {
try {
responseConsumer.accept(new CacheFetchedProfile(profile));
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception thrown processing cached profile!", t);
}
throw new CancelException();
}
}
public static void lookupProfileByUsername(String playerUsername, ICacheProvider cacheProvider, Consumer<CacheFetchedProfile> responseConsumer) {
String playerUsernameLower = playerUsername.toLowerCase();
CacheLoadedProfile profile = cacheProvider.loadProfileByUsername(playerUsernameLower);
if(profile == null) {
if(!playerUsernameLower.equals(playerUsernameLower.replaceAll("[^a-z0-9_]", "_").trim())) {
try {
responseConsumer.accept(null);
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception thrown processing invalid profile!", t);
}
throw new CancelException();
}
URI requestURI = URI.create("https://api.mojang.com/users/profiles/minecraft/" + playerUsername);
int globalRatelimit = EaglerXBungee.getEagler().getConfig().getUuidRateLimitGlobal();
boolean isRateLimit;
synchronized(rateLimitLookup) {
isRateLimit = !rateLimitLookup.rateLimit(globalRatelimit);
}
if(!isRateLimit) {
BinaryHttpClient.asyncRequest("GET", requestURI, new UsernameToUUIDConsumerImpl(playerUsername, cacheProvider, responseConsumer));
}else {
EaglerXBungee.logger().warning("skin system reached the global UUID lookup ratelimit of " + globalRatelimit + " while looking up \"" + playerUsername + "\"");
throw new CancelException();
}
}else {
try {
responseConsumer.accept(new CacheFetchedProfile(profile));
}catch(Throwable t) {
EaglerXBungee.logger().log(Level.SEVERE, "Exception thrown processing cached profile!", t);
}
throw new CancelException();
}
}
public static class CancelException extends RuntimeException {
}
}

View File

@ -0,0 +1,259 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.net.ssl.SSLEngine;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.md_5.bungee.netty.PipelineUtils;
/**
* 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 BinaryHttpClient {
public static class Response {
public final int code;
public final byte[] data;
public final Throwable exception;
public Response(int code, byte[] data) {
this.code = code;
this.data = data;
this.exception = null;
}
public Response(Throwable exception) {
this.code = -1;
this.data = null;
this.exception = exception;
}
}
private static class NettyHttpChannelFutureListener implements ChannelFutureListener {
protected final String method;
protected final URI requestURI;
protected final Consumer<Response> responseCallback;
protected NettyHttpChannelFutureListener(String method, URI requestURI, Consumer<Response> responseCallback) {
this.method = method;
this.requestURI = requestURI;
this.responseCallback = responseCallback;
}
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
String path = requestURI.getRawPath()
+ ((requestURI.getRawQuery() == null) ? "" : ("?" + requestURI.getRawQuery()));
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1,
HttpMethod.valueOf(method), path);
request.headers().set(HttpHeaderNames.HOST, (Object) requestURI.getHost());
request.headers().set(HttpHeaderNames.USER_AGENT, "Mozilla/5.0 EaglerXBungee/" + EaglerXBungee.getEagler().getDescription().getVersion());
future.channel().writeAndFlush(request);
} else {
addressCache.invalidate(requestURI.getHost());
responseCallback.accept(new Response(new IOException("Connection failed")));
}
}
}
private static class NettyHttpChannelInitializer extends ChannelInitializer<Channel> {
protected final Consumer<Response> responseCallback;
protected final boolean ssl;
protected final String host;
protected final int port;
protected NettyHttpChannelInitializer(Consumer<Response> responseCallback, boolean ssl, String host, int port) {
this.responseCallback = responseCallback;
this.ssl = ssl;
this.host = host;
this.port = port;
}
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast("timeout", new ReadTimeoutHandler(5L, TimeUnit.SECONDS));
if (this.ssl) {
SSLEngine engine = SslContextBuilder.forClient().build().newEngine(ch.alloc(), host, port);
ch.pipeline().addLast("ssl", new SslHandler(engine));
}
ch.pipeline().addLast("http", new HttpClientCodec());
ch.pipeline().addLast("handler", new NettyHttpResponseHandler(responseCallback));
}
}
private static class NettyHttpResponseHandler extends SimpleChannelInboundHandler<HttpObject> {
protected final Consumer<Response> responseCallback;
protected int responseCode = -1;
protected ByteBuf buffer = null;
protected NettyHttpResponseHandler(Consumer<Response> responseCallback) {
this.responseCallback = responseCallback;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (msg instanceof HttpResponse) {
HttpResponse response = (HttpResponse) msg;
responseCode = response.status().code();
if (responseCode == HttpResponseStatus.NO_CONTENT.code()) {
this.done(ctx);
return;
}
}
if (msg instanceof HttpContent) {
HttpContent content = (HttpContent) msg;
if(buffer == null) {
buffer = ctx.alloc().buffer();
}
this.buffer.writeBytes(content.content());
if (msg instanceof LastHttpContent) {
this.done(ctx);
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
responseCallback.accept(new Response(cause));
}
private void done(ChannelHandlerContext ctx) {
try {
byte[] array;
if(buffer != null) {
array = new byte[buffer.readableBytes()];
buffer.readBytes(array);
buffer.release();
}else {
array = new byte[0];
}
responseCallback.accept(new Response(responseCode, array));
}finally {
ctx.channel().pipeline().remove(this);
ctx.channel().close();
}
}
}
private static final Cache<String, InetAddress> addressCache = CacheBuilder.newBuilder().expireAfterWrite(15L, TimeUnit.MINUTES).build();
private static EventLoopGroup eventLoop = null;
public static void asyncRequest(String method, URI uri, Consumer<Response> responseCallback) {
EventLoopGroup eventLoop = getEventLoopGroup();
int port = uri.getPort();
boolean ssl = false;
String scheme = uri.getScheme();
switch(scheme) {
case "http":
if(port == -1) {
port = 80;
}
break;
case "https":
if(port == -1) {
port = 443;
}
ssl = true;
break;
default:
responseCallback.accept(new Response(new UnsupportedOperationException("Unsupported scheme: " + scheme)));
return;
}
String host = uri.getHost();
InetAddress inetHost = addressCache.getIfPresent(host);
if (inetHost == null) {
try {
inetHost = InetAddress.getByName(host);
} catch (UnknownHostException ex) {
responseCallback.accept(new Response(ex));
return;
}
addressCache.put(host, inetHost);
}
(new Bootstrap()).channel(PipelineUtils.getChannel(null)).group(eventLoop)
.handler(new NettyHttpChannelInitializer(responseCallback, ssl, host, port))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000).option(ChannelOption.TCP_NODELAY, true)
.remoteAddress(inetHost, port).connect()
.addListener(new NettyHttpChannelFutureListener(method, uri, responseCallback));
}
private static EventLoopGroup getEventLoopGroup() {
if(eventLoop == null) {
eventLoop = PipelineUtils.newEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Skin Download Thread #%1$d").build());
}
return eventLoop;
}
public static void killEventLoop() {
if(eventLoop != null) {
EaglerXBungee.logger().info("Stopping skin cache HTTP client...");
eventLoop.shutdownGracefully();
try {
eventLoop.awaitTermination(30l, TimeUnit.SECONDS);
} catch (InterruptedException var13) {
;
}
eventLoop = null;
}
}
}

View File

@ -0,0 +1,110 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.util.UUID;
import net.md_5.bungee.UserConnection;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class CapePackets {
public static final int PACKET_MY_CAPE_PRESET = 0x01;
public static final int PACKET_MY_CAPE_CUSTOM = 0x02;
public static final int PACKET_GET_OTHER_CAPE = 0x03;
public static final int PACKET_OTHER_CAPE_PRESET = 0x04;
public static final int PACKET_OTHER_CAPE_CUSTOM = 0x05;
public static void processPacket(byte[] data, UserConnection sender, CapeServiceOffline capeService) throws IOException {
if(data.length == 0) {
throw new IOException("Zero-length packet recieved");
}
int packetId = (int)data[0] & 0xFF;
try {
switch(packetId) {
case PACKET_GET_OTHER_CAPE:
processGetOtherCape(data, sender, capeService);
break;
default:
throw new IOException("Unknown packet type " + packetId);
}
}catch(IOException ex) {
throw ex;
}catch(Throwable t) {
throw new IOException("Unhandled exception handling cape packet type " + packetId, t);
}
}
private static void processGetOtherCape(byte[] data, UserConnection sender, CapeServiceOffline capeService) throws IOException {
if(data.length != 17) {
throw new IOException("Invalid length " + data.length + " for skin request packet");
}
UUID searchUUID = SkinPackets.bytesToUUID(data, 1);
capeService.processGetOtherCape(searchUUID, sender);
}
public static void registerEaglerPlayer(UUID clientUUID, byte[] bs, CapeServiceOffline capeService) throws IOException {
if(bs.length == 0) {
throw new IOException("Zero-length packet recieved");
}
byte[] generatedPacket;
int packetType = (int)bs[0] & 0xFF;
switch(packetType) {
case PACKET_MY_CAPE_PRESET:
if(bs.length != 5) {
throw new IOException("Invalid length " + bs.length + " for preset cape packet");
}
generatedPacket = CapePackets.makePresetResponse(clientUUID, (bs[1] << 24) | (bs[2] << 16) | (bs[3] << 8) | (bs[4] & 0xFF));
break;
case PACKET_MY_CAPE_CUSTOM:
if(bs.length != 1174) {
throw new IOException("Invalid length " + bs.length + " for custom cape packet");
}
generatedPacket = CapePackets.makeCustomResponse(clientUUID, bs, 1, 1173);
break;
default:
throw new IOException("Unknown skin packet type: " + packetType);
}
capeService.registerEaglercraftPlayer(clientUUID, generatedPacket);
}
public static void registerEaglerPlayerFallback(UUID clientUUID, CapeServiceOffline capeService) {
capeService.registerEaglercraftPlayer(clientUUID, CapePackets.makePresetResponse(clientUUID, 0));
}
public static byte[] makePresetResponse(UUID uuid, int presetId) {
byte[] ret = new byte[1 + 16 + 4];
ret[0] = (byte)PACKET_OTHER_CAPE_PRESET;
SkinPackets.UUIDToBytes(uuid, ret, 1);
ret[17] = (byte)(presetId >> 24);
ret[18] = (byte)(presetId >> 16);
ret[19] = (byte)(presetId >> 8);
ret[20] = (byte)(presetId & 0xFF);
return ret;
}
public static byte[] makeCustomResponse(UUID uuid, byte[] pixels) {
return makeCustomResponse(uuid, pixels, 0, pixels.length);
}
public static byte[] makeCustomResponse(UUID uuid, byte[] pixels, int offset, int length) {
byte[] ret = new byte[1 + 16 + length];
ret[0] = (byte)PACKET_OTHER_CAPE_CUSTOM;
SkinPackets.UUIDToBytes(uuid, ret, 1);
System.arraycopy(pixels, offset, ret, 17, length);
return ret;
}
}

View File

@ -0,0 +1,64 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.md_5.bungee.UserConnection;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class CapeServiceOffline {
public static final int masterRateLimitPerPlayer = 250;
public static final String CHANNEL = "EAG|Capes-1.8";
private final Map<UUID, byte[]> capesCache = new HashMap();
public void registerEaglercraftPlayer(UUID playerUUID, byte[] capePacket) {
synchronized(capesCache) {
capesCache.put(playerUUID, capePacket);
}
}
public void processGetOtherCape(UUID searchUUID, UserConnection sender) {
if(((EaglerInitialHandler)sender.getPendingConnection()).skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
byte[] maybeCape;
synchronized(capesCache) {
maybeCape = capesCache.get(searchUUID);
}
if(maybeCape != null) {
sender.sendData(CapeServiceOffline.CHANNEL, maybeCape);
}else {
sender.sendData(CapeServiceOffline.CHANNEL, CapePackets.makePresetResponse(searchUUID, 0));
}
}
}
public void unregisterPlayer(UUID playerUUID) {
synchronized(capesCache) {
capesCache.remove(playerUUID);
}
}
public void shutdown() {
synchronized(capesCache) {
capesCache.clear();
}
}
}

View File

@ -0,0 +1,90 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.util.UUID;
/**
* 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 interface ICacheProvider {
public static class CacheException extends RuntimeException {
public CacheException() {
super();
}
public CacheException(String message, Throwable cause) {
super(message, cause);
}
public CacheException(String message) {
super(message);
}
public CacheException(Throwable cause) {
super(cause);
}
}
public static class CacheLoadedSkin {
public final UUID uuid;
public final String url;
public final byte[] texture;
public CacheLoadedSkin(UUID uuid, String url, byte[] texture) {
this.uuid = uuid;
this.url = url;
this.texture = texture;
}
}
public static class CacheLoadedProfile {
public final UUID uuid;
public final String username;
public final String texture;
public final String model;
public CacheLoadedProfile(UUID uuid, String username, String texture, String model) {
this.uuid = uuid;
this.username = username;
this.texture = texture;
this.model = model;
}
public UUID getSkinUUID() {
return SkinPackets.createEaglerURLSkinUUID(texture);
}
}
CacheLoadedSkin loadSkinByUUID(UUID uuid) throws CacheException;
void cacheSkinByUUID(UUID uuid, String url, byte[] textureBlob) throws CacheException;
CacheLoadedProfile loadProfileByUUID(UUID uuid) throws CacheException;
CacheLoadedProfile loadProfileByUsername(String username) throws CacheException;
void cacheProfileByUUID(UUID uuid, String username, String texture, String model) throws CacheException;
void flush() throws CacheException;
void destroy();
}

View File

@ -0,0 +1,46 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.util.UUID;
import net.md_5.bungee.UserConnection;
/**
* Copyright (c) 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 interface ISkinService {
void init(String uri, String driverClass, String driverPath, int keepObjectsDays, int keepProfilesDays,
int maxObjects, int maxProfiles);
void processGetOtherSkin(final UUID searchUUID, final UserConnection sender);
void processGetOtherSkin(UUID searchUUID, String skinURL, UserConnection sender);
void registerEaglercraftPlayer(UUID clientUUID, byte[] generatedPacket, int modelId) throws IOException;
void unregisterPlayer(UUID clientUUID);
default void registerTextureToPlayerAssociation(String textureURL, UUID playerUUID) {
registerTextureToPlayerAssociation(SkinPackets.createEaglerURLSkinUUID(textureURL), playerUUID);
}
void registerTextureToPlayerAssociation(UUID textureUUID, UUID playerUUID);
void flush();
void shutdown();
}

View File

@ -0,0 +1,403 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.UUID;
import java.util.logging.Level;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.sqlite.EaglerDrivers;
/**
* 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 JDBCCacheProvider implements ICacheProvider {
public static JDBCCacheProvider initialize(String uri, String driverClass, String driverPath, int keepObjectsDays,
int keepProfilesDays, int maxObjects, int maxProfiles) throws CacheException {
Connection conn;
try {
conn = EaglerDrivers.connectToDatabase(uri, driverClass, driverPath, new Properties());
if(conn == null) {
throw new IllegalStateException("Connection is null");
}
}catch(Throwable t) {
throw new CacheException("Could not initialize '" + uri + "'!", t);
}
EaglerXBungee.logger().info("Connected to database: " + uri);
try {
try(Statement stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE IF NOT EXISTS "
+ "\"eaglercraft_skins_objects\" ("
+ "\"TextureUUID\" TEXT(32) NOT NULL,"
+ "\"TextureURL\" VARCHAR(256) NOT NULL,"
+ "\"TextureTime\" DATETIME NOT NULL,"
+ "\"TextureData\" BLOB,"
+ "\"TextureLength\" INT(24) NOT NULL,"
+ "PRIMARY KEY(\"TextureUUID\"))");
stmt.execute("CREATE TABLE IF NOT EXISTS "
+ "\"eaglercraft_skins_profiles\" ("
+ "\"ProfileUUID\" TEXT(32) NOT NULL,"
+ "\"ProfileName\" TEXT(16) NOT NULL,"
+ "\"ProfileTime\" DATETIME NOT NULL,"
+ "\"ProfileTexture\" VARCHAR(256),"
+ "\"ProfileModel\" VARCHAR(16) NOT NULL,"
+ "PRIMARY KEY(\"ProfileUUID\"))");
stmt.execute("CREATE INDEX IF NOT EXISTS \"profile_name_index\" "
+ "ON \"eaglercraft_skins_profiles\" (\"ProfileName\")");
}
JDBCCacheProvider cacheProvider = new JDBCCacheProvider(conn, uri, keepObjectsDays, keepProfilesDays, maxObjects, maxProfiles);
cacheProvider.flush();
return cacheProvider;
}catch(CacheException ex) {
try {
conn.close();
}catch(SQLException exx) {
}
throw ex;
}catch(Throwable t) {
try {
conn.close();
}catch(SQLException exx) {
}
throw new CacheException("Could not initialize '" + uri + "'!", t);
}
}
protected final Connection connection;
protected final String uri;
protected final PreparedStatement discardExpiredObjects;
protected final PreparedStatement discardExpiredProfiles;
protected final PreparedStatement getTotalObjects;
protected final PreparedStatement getTotalProfiles;
protected final PreparedStatement deleteSomeOldestObjects;
protected final PreparedStatement deleteSomeOldestProfiles;
protected final PreparedStatement querySkinByUUID;
protected final PreparedStatement queryProfileByUUID;
protected final PreparedStatement queryProfileByUsername;
protected final PreparedStatement cacheNewSkin;
protected final PreparedStatement cacheNewProfile;
protected final PreparedStatement cacheHasSkin;
protected final PreparedStatement cacheHasProfile;
protected final PreparedStatement cacheUpdateSkin;
protected final PreparedStatement cacheUpdateProfile;
protected long lastFlush;
protected int keepObjectsDays;
protected int keepProfilesDays;
protected int maxObjects;
protected int maxProfiles;
protected JDBCCacheProvider(Connection conn, String uri, int keepObjectsDays, int keepProfilesDays, int maxObjects,
int maxProfiles) throws SQLException {
this.connection = conn;
this.uri = uri;
this.lastFlush = 0l;
this.keepObjectsDays = keepObjectsDays;
this.keepProfilesDays = keepProfilesDays;
this.maxObjects = maxObjects;
this.maxProfiles = maxProfiles;
this.discardExpiredObjects = connection.prepareStatement("DELETE FROM eaglercraft_skins_objects WHERE textureTime < ?");
this.discardExpiredProfiles = connection.prepareStatement("DELETE FROM eaglercraft_skins_profiles WHERE profileTime < ?");
this.getTotalObjects = connection.prepareStatement("SELECT COUNT(*) AS total_objects FROM eaglercraft_skins_objects");
this.getTotalProfiles = connection.prepareStatement("SELECT COUNT(*) AS total_profiles FROM eaglercraft_skins_profiles");
this.deleteSomeOldestObjects = connection.prepareStatement("DELETE FROM eaglercraft_skins_objects WHERE TextureUUID IN (SELECT TextureUUID FROM eaglercraft_skins_objects ORDER BY TextureTime ASC LIMIT ?)");
this.deleteSomeOldestProfiles = connection.prepareStatement("DELETE FROM eaglercraft_skins_profiles WHERE ProfileUUID IN (SELECT ProfileUUID FROM eaglercraft_skins_profiles ORDER BY ProfileTime ASC LIMIT ?)");
this.querySkinByUUID = connection.prepareStatement("SELECT TextureURL,TextureData,TextureLength FROM eaglercraft_skins_objects WHERE TextureUUID = ? LIMIT 1");
this.queryProfileByUUID = connection.prepareStatement("SELECT ProfileName,ProfileTexture,ProfileModel FROM eaglercraft_skins_profiles WHERE ProfileUUID = ? LIMIT 1");
this.queryProfileByUsername = connection.prepareStatement("SELECT ProfileUUID,ProfileTexture,ProfileModel FROM eaglercraft_skins_profiles WHERE ProfileName = ? LIMIT 1");
this.cacheNewSkin = connection.prepareStatement("INSERT INTO eaglercraft_skins_objects (TextureUUID, TextureURL, TextureTime, TextureData, TextureLength) VALUES(?, ?, ?, ?, ?)");
this.cacheNewProfile = connection.prepareStatement("INSERT INTO eaglercraft_skins_profiles (ProfileUUID, ProfileName, ProfileTime, ProfileTexture, ProfileModel) VALUES(?, ?, ?, ?, ?)");
this.cacheHasSkin = connection.prepareStatement("SELECT COUNT(TextureUUID) AS has_object FROM eaglercraft_skins_objects WHERE TextureUUID = ? LIMIT 1");
this.cacheHasProfile = connection.prepareStatement("SELECT COUNT(ProfileUUID) AS has_profile FROM eaglercraft_skins_profiles WHERE ProfileUUID = ? LIMIT 1");
this.cacheUpdateSkin = connection.prepareStatement("UPDATE eaglercraft_skins_objects SET TextureURL = ?, TextureTime = ?, TextureData = ?, TextureLength = ? WHERE TextureUUID = ?");
this.cacheUpdateProfile = connection.prepareStatement("UPDATE eaglercraft_skins_profiles SET ProfileName = ?, ProfileTime = ?, ProfileTexture = ?, ProfileModel = ? WHERE ProfileUUID = ?");
}
public CacheLoadedSkin loadSkinByUUID(UUID uuid) throws CacheException {
String uuidString = SkinService.getMojangUUID(uuid);
String queriedUrls;
byte[] queriedTexture;
int queriedLength;
try {
synchronized(querySkinByUUID) {
querySkinByUUID.setString(1, uuidString);
try(ResultSet resultSet = querySkinByUUID.executeQuery()) {
if(resultSet.next()) {
queriedUrls = resultSet.getString(1);
queriedTexture = resultSet.getBytes(2);
queriedLength = resultSet.getInt(3);
}else {
return null;
}
}
}
}catch(SQLException ex) {
throw new CacheException("SQL query failure while loading cached skin", ex);
}
if(queriedLength == 0) {
return new CacheLoadedSkin(uuid, queriedUrls, new byte[0]);
}else {
byte[] decompressed = new byte[queriedLength];
try {
GZIPInputStream is = new GZIPInputStream(new ByteArrayInputStream(queriedTexture));
int i = 0, j = 0;
while(j < queriedLength && (i = is.read(decompressed, j, queriedLength - j)) != -1) {
j += i;
}
}catch(IOException ex) {
throw new CacheException("SQL query failure while loading cached skin");
}
return new CacheLoadedSkin(uuid, queriedUrls, decompressed);
}
}
public void cacheSkinByUUID(UUID uuid, String url, byte[] textureBlob) throws CacheException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
try {
GZIPOutputStream deflateOut = new GZIPOutputStream(bao);
deflateOut.write(textureBlob);
deflateOut.close();
}catch(IOException ex) {
throw new CacheException("Skin compression error", ex);
}
int len;
byte[] textureBlobCompressed;
if(textureBlob == null || textureBlob.length == 0) {
len = 0;
textureBlobCompressed = null;
}else {
len = textureBlob.length;
textureBlobCompressed = bao.toByteArray();
}
try {
String uuidString = SkinService.getMojangUUID(uuid);
synchronized(cacheNewSkin) {
boolean has;
cacheHasSkin.setString(1, uuidString);
try(ResultSet resultSet = cacheHasSkin.executeQuery()) {
if(resultSet.next()) {
has = resultSet.getInt(1) > 0;
}else {
has = false; // ??
}
}
if(has) {
cacheUpdateSkin.setString(1, url);
cacheUpdateSkin.setDate(2, new Date(System.currentTimeMillis()));
cacheUpdateSkin.setBytes(3, textureBlobCompressed);
cacheUpdateSkin.setInt(4, len);
cacheUpdateSkin.setString(5, uuidString);
cacheUpdateSkin.executeUpdate();
}else {
cacheNewSkin.setString(1, uuidString);
cacheNewSkin.setString(2, url);
cacheNewSkin.setDate(3, new Date(System.currentTimeMillis()));
cacheNewSkin.setBytes(4, textureBlobCompressed);
cacheNewSkin.setInt(5, len);
cacheNewSkin.executeUpdate();
}
}
}catch(SQLException ex) {
throw new CacheException("SQL query failure while caching new skin", ex);
}
}
public CacheLoadedProfile loadProfileByUUID(UUID uuid) throws CacheException {
try {
String uuidString = SkinService.getMojangUUID(uuid);
synchronized(queryProfileByUUID) {
queryProfileByUUID.setString(1, uuidString);
try(ResultSet resultSet = queryProfileByUUID.executeQuery()) {
if(resultSet.next()) {
String profileName = resultSet.getString(1);
String profileTexture = resultSet.getString(2);
String profileModel = resultSet.getString(3);
return new CacheLoadedProfile(uuid, profileName, profileTexture, profileModel);
}else {
return null;
}
}
}
}catch(SQLException ex) {
throw new CacheException("SQL query failure while loading profile by uuid", ex);
}
}
public CacheLoadedProfile loadProfileByUsername(String username) throws CacheException {
try {
synchronized(queryProfileByUsername) {
queryProfileByUsername.setString(1, username);
try(ResultSet resultSet = queryProfileByUsername.executeQuery()) {
if(resultSet.next()) {
UUID profileUUID = SkinService.parseMojangUUID(resultSet.getString(1));
String profileTexture = resultSet.getString(2);
String profileModel = resultSet.getString(3);
return new CacheLoadedProfile(profileUUID, username, profileTexture, profileModel);
}else {
return null;
}
}
}
}catch(SQLException ex) {
throw new CacheException("SQL query failure while loading profile by username", ex);
}
}
public void cacheProfileByUUID(UUID uuid, String username, String texture, String model) throws CacheException {
try {
String uuidString = SkinService.getMojangUUID(uuid);
synchronized(cacheNewProfile) {
boolean has;
cacheHasProfile.setString(1, uuidString);
try(ResultSet resultSet = cacheHasProfile.executeQuery()) {
if(resultSet.next()) {
has = resultSet.getInt(1) > 0;
}else {
has = false; // ??
}
}
if(has) {
cacheUpdateProfile.setString(1, username);
cacheUpdateProfile.setDate(2, new Date(System.currentTimeMillis()));
cacheUpdateProfile.setString(3, texture);
cacheUpdateProfile.setString(4, model);
cacheUpdateProfile.setString(5, uuidString);
cacheUpdateProfile.executeUpdate();
}else {
cacheNewProfile.setString(1, uuidString);
cacheNewProfile.setString(2, username);
cacheNewProfile.setDate(3, new Date(System.currentTimeMillis()));
cacheNewProfile.setString(4, texture);
cacheNewProfile.setString(5, model);
cacheNewProfile.executeUpdate();
}
}
}catch(SQLException ex) {
throw new CacheException("SQL query failure while caching new profile", ex);
}
}
@Override
public void flush() {
long millis = System.currentTimeMillis();
if(millis - lastFlush > 1200000l) { // 30 minutes
lastFlush = millis;
try {
Date expiryObjects = new Date(millis - keepObjectsDays * 86400000l);
Date expiryProfiles = new Date(millis - keepProfilesDays * 86400000l);
synchronized(discardExpiredObjects) {
discardExpiredObjects.setDate(1, expiryObjects);
discardExpiredObjects.execute();
}
synchronized(discardExpiredProfiles) {
discardExpiredProfiles.setDate(1, expiryProfiles);
discardExpiredProfiles.execute();
}
int totalObjects, totalProfiles;
synchronized(getTotalObjects) {
try(ResultSet resultSet = getTotalObjects.executeQuery()) {
if(resultSet.next()) {
totalObjects = resultSet.getInt(1);
}else {
throw new SQLException("Empty ResultSet recieved when checking \"eaglercraft_skins_objects\" row count");
}
}
}
synchronized(getTotalProfiles) {
try(ResultSet resultSet = getTotalProfiles.executeQuery()) {
if(resultSet.next()) {
totalProfiles = resultSet.getInt(1);
}else {
throw new SQLException("Empty ResultSet recieved when checking \"eaglercraft_skins_profiles\" row count");
}
}
}
if(totalObjects > maxObjects) {
int deleteCount = totalObjects - maxObjects + (maxObjects >> 3);
EaglerXBungee.logger().warning("Skin object cache has passed " + maxObjects + " skins in size ("
+ totalObjects + "), deleting " + deleteCount + " skins from the cache to free space");
synchronized(deleteSomeOldestObjects) {
deleteSomeOldestObjects.setInt(1, deleteCount);
deleteSomeOldestObjects.executeUpdate();
}
}
if(totalProfiles > maxProfiles) {
int deleteCount = totalProfiles - maxProfiles + (maxProfiles >> 3);
EaglerXBungee.logger().warning("Skin profile cache has passed " + maxProfiles + " profiles in size ("
+ totalProfiles + "), deleting " + deleteCount + " profiles from the cache to free space");
synchronized(deleteSomeOldestProfiles) {
deleteSomeOldestProfiles.setInt(1, deleteCount);
deleteSomeOldestProfiles.executeUpdate();
}
}
}catch(SQLException ex) {
throw new CacheException("SQL query failure while flushing cache!", ex);
}
}
}
private void destroyStatement(Statement stmt) {
try {
stmt.close();
} catch (SQLException e) {
}
}
@Override
public void destroy() {
destroyStatement(discardExpiredObjects);
destroyStatement(discardExpiredProfiles);
destroyStatement(getTotalObjects);
destroyStatement(getTotalProfiles);
destroyStatement(deleteSomeOldestObjects);
destroyStatement(deleteSomeOldestProfiles);
destroyStatement(querySkinByUUID);
destroyStatement(queryProfileByUUID);
destroyStatement(queryProfileByUsername);
destroyStatement(cacheNewSkin);
destroyStatement(cacheNewProfile);
destroyStatement(cacheHasSkin);
destroyStatement(cacheHasProfile);
destroyStatement(cacheUpdateSkin);
destroyStatement(cacheUpdateProfile);
try {
connection.close();
EaglerXBungee.logger().info("Successfully disconnected from database '" + uri + "'");
} catch (SQLException e) {
EaglerXBungee.logger().log(Level.WARNING, "Exception disconnecting from database '" + uri + "'!", e);
}
}
}

View File

@ -0,0 +1,47 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
/**
* 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 SimpleRateLimiter {
private long timer;
private int count;
public SimpleRateLimiter() {
timer = System.currentTimeMillis();
count = 0;
}
public boolean rateLimit(int maxPerMinute) {
int t = 60000 / maxPerMinute;
long millis = System.currentTimeMillis();
int decr = (int)(millis - timer) / t;
if(decr > 0) {
timer += decr * t;
count -= decr;
if(count < 0) {
count = 0;
}
}
if(count >= maxPerMinute) {
return false;
}else {
++count;
return true;
}
}
}

View File

@ -0,0 +1,259 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.md_5.bungee.UserConnection;
/**
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class SkinPackets {
public static final int PACKET_MY_SKIN_PRESET = 0x01;
public static final int PACKET_MY_SKIN_CUSTOM = 0x02;
public static final int PACKET_GET_OTHER_SKIN = 0x03;
public static final int PACKET_OTHER_SKIN_PRESET = 0x04;
public static final int PACKET_OTHER_SKIN_CUSTOM = 0x05;
public static final int PACKET_GET_SKIN_BY_URL = 0x06;
public static void processPacket(byte[] data, UserConnection sender, ISkinService skinService) throws IOException {
if(data.length == 0) {
throw new IOException("Zero-length packet recieved");
}
int packetId = (int)data[0] & 0xFF;
try {
switch(packetId) {
case PACKET_GET_OTHER_SKIN:
processGetOtherSkin(data, sender, skinService);
break;
case PACKET_GET_SKIN_BY_URL:
processGetOtherSkinByURL(data, sender, skinService);
break;
default:
throw new IOException("Unknown packet type " + packetId);
}
}catch(IOException ex) {
throw ex;
}catch(Throwable t) {
throw new IOException("Unhandled exception handling packet type " + packetId, t);
}
}
private static void processGetOtherSkin(byte[] data, UserConnection sender, ISkinService skinService) throws IOException {
if(data.length != 17) {
throw new IOException("Invalid length " + data.length + " for skin request packet");
}
UUID searchUUID = bytesToUUID(data, 1);
skinService.processGetOtherSkin(searchUUID, sender);
}
private static void processGetOtherSkinByURL(byte[] data, UserConnection sender, ISkinService skinService) throws IOException {
if(data.length < 20) {
throw new IOException("Invalid length " + data.length + " for skin request packet");
}
UUID searchUUID = bytesToUUID(data, 1);
int urlLength = (data[17] << 8) | data[18];
if(data.length < 19 + urlLength) {
throw new IOException("Invalid length " + data.length + " for skin request packet with " + urlLength + " length URL");
}
String urlStr = bytesToAscii(data, 19, urlLength);
urlStr = SkinService.sanitizeTextureURL(urlStr);
if(urlStr == null) {
throw new IOException("Invalid URL for skin request packet");
}
URL url;
try {
url = new URL(urlStr);
}catch(MalformedURLException t) {
throw new IOException("Invalid URL for skin request packet", t);
}
String host = url.getHost();
if(EaglerXBungee.getEagler().getConfig().isValidSkinHost(host)) {
UUID validUUID = createEaglerURLSkinUUID(urlStr);
if(!searchUUID.equals(validUUID)) {
throw new IOException("Invalid generated UUID from skin URL");
}
skinService.processGetOtherSkin(searchUUID, urlStr, sender);
}else {
throw new IOException("Invalid host in skin packet: " + host);
}
}
public static void registerEaglerPlayer(UUID clientUUID, byte[] bs, ISkinService skinService) throws IOException {
if(bs.length == 0) {
throw new IOException("Zero-length packet recieved");
}
byte[] generatedPacket;
int skinModel = -1;
int packetType = (int)bs[0] & 0xFF;
switch(packetType) {
case PACKET_MY_SKIN_PRESET:
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));
break;
case PACKET_MY_SKIN_CUSTOM:
if(bs.length != 2 + 16384) {
throw new IOException("Invalid length " + bs.length + " for custom skin packet");
}
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);
}
public static void registerEaglerPlayerFallback(UUID clientUUID, ISkinService skinService) throws IOException {
int skinModel = (clientUUID.hashCode() & 1) != 0 ? 1 : 0;
byte[] generatedPacket = SkinPackets.makePresetResponse(clientUUID, skinModel);
skinService.registerEaglercraftPlayer(clientUUID, generatedPacket, skinModel);
}
public static void setAlphaForChest(byte[] skin64x64, byte alpha, int offset) {
if(skin64x64.length - offset != 16384) {
throw new IllegalArgumentException("Skin is not 64x64!");
}
for(int y = 20; y < 32; ++y) {
for(int x = 16; x < 40; ++x) {
skin64x64[offset + ((y << 8) | (x << 2))] = alpha;
}
}
}
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);
}
return new String(ret);
}
public static String bytesToAscii(byte[] bytes, int off, int len) {
char[] ret = new char[len];
for(int i = 0; i < len; ++i) {
ret[i] = (char)((int)bytes[off + i] & 0xFF);
}
return new String(ret);
}
public static String bytesToAscii(byte[] bytes) {
return bytesToAscii(bytes, 0, bytes.length);
}
public static void UUIDToBytes(UUID uuid, byte[] bytes, int off) {
long msb = uuid.getMostSignificantBits();
long lsb = uuid.getLeastSignificantBits();
bytes[off] = (byte)(msb >> 56l);
bytes[off + 1] = (byte)(msb >> 48l);
bytes[off + 2] = (byte)(msb >> 40l);
bytes[off + 3] = (byte)(msb >> 32l);
bytes[off + 4] = (byte)(msb >> 24l);
bytes[off + 5] = (byte)(msb >> 16l);
bytes[off + 6] = (byte)(msb >> 8l);
bytes[off + 7] = (byte)(msb & 0xFFl);
bytes[off + 8] = (byte)(lsb >> 56l);
bytes[off + 9] = (byte)(lsb >> 48l);
bytes[off + 10] = (byte)(lsb >> 40l);
bytes[off + 11] = (byte)(lsb >> 32l);
bytes[off + 12] = (byte)(lsb >> 24l);
bytes[off + 13] = (byte)(lsb >> 16l);
bytes[off + 14] = (byte)(lsb >> 8l);
bytes[off + 15] = (byte)(lsb & 0xFFl);
}
public static byte[] asciiString(String string) {
byte[] str = new byte[string.length()];
for(int i = 0; i < str.length; ++i) {
str[i] = (byte)string.charAt(i);
}
return str;
}
public static UUID createEaglerURLSkinUUID(String skinUrl) {
return UUID.nameUUIDFromBytes(asciiString("EaglercraftSkinURL:" + skinUrl));
}
public static int getModelId(String modelName) {
return "slim".equalsIgnoreCase(modelName) ? 1 : 0;
}
public static byte[] rewriteUUID(UUID newUUID, byte[] pkt) {
byte[] ret = new byte[pkt.length];
System.arraycopy(pkt, 0, ret, 0, pkt.length);
UUIDToBytes(newUUID, ret, 1);
return ret;
}
public static byte[] rewriteUUIDModel(UUID newUUID, byte[] pkt, int model) {
byte[] ret = new byte[pkt.length];
System.arraycopy(pkt, 0, ret, 0, pkt.length);
UUIDToBytes(newUUID, ret, 1);
if(ret[0] == (byte)PACKET_OTHER_SKIN_CUSTOM) {
ret[17] = (byte)model;
}
return ret;
}
}

View File

@ -0,0 +1,76 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
/**
* 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 SkinRescaler {
public static void convertToBytes(int[] imageIn, byte[] imageOut) {
for(int i = 0, j, k; i < imageIn.length; ++i) {
j = i << 2;
k = imageIn[i];
imageOut[j] = (byte)(k >> 24);
imageOut[j + 1] = (byte)(k & 0xFF);
imageOut[j + 2] = (byte)(k >> 8);
imageOut[j + 3] = (byte)(k >> 16);
}
}
public static void convert64x32To64x64(int[] imageIn, byte[] imageOut) {
copyRawPixels(imageIn, imageOut, 0, 0, 0, 0, 64, 32, 64, 64, false);
copyRawPixels(imageIn, imageOut, 24, 48, 20, 52, 4, 16, 8, 20, 64, 64);
copyRawPixels(imageIn, imageOut, 28, 48, 24, 52, 8, 16, 12, 20, 64, 64);
copyRawPixels(imageIn, imageOut, 20, 52, 16, 64, 8, 20, 12, 32, 64, 64);
copyRawPixels(imageIn, imageOut, 24, 52, 20, 64, 4, 20, 8, 32, 64, 64);
copyRawPixels(imageIn, imageOut, 28, 52, 24, 64, 0, 20, 4, 32, 64, 64);
copyRawPixels(imageIn, imageOut, 32, 52, 28, 64, 12, 20, 16, 32, 64, 64);
copyRawPixels(imageIn, imageOut, 40, 48, 36, 52, 44, 16, 48, 20, 64, 64);
copyRawPixels(imageIn, imageOut, 44, 48, 40, 52, 48, 16, 52, 20, 64, 64);
copyRawPixels(imageIn, imageOut, 36, 52, 32, 64, 48, 20, 52, 32, 64, 64);
copyRawPixels(imageIn, imageOut, 40, 52, 36, 64, 44, 20, 48, 32, 64, 64);
copyRawPixels(imageIn, imageOut, 44, 52, 40, 64, 40, 20, 44, 32, 64, 64);
copyRawPixels(imageIn, imageOut, 48, 52, 44, 64, 52, 20, 56, 32, 64, 64);
}
private static void copyRawPixels(int[] imageIn, byte[] imageOut, int dx1, int dy1, int dx2, int dy2, int sx1,
int sy1, int sx2, int sy2, int imgSrcWidth, int imgDstWidth) {
if(dx1 > dx2) {
copyRawPixels(imageIn, imageOut, sx1, sy1, dx2, dy1, sx2 - sx1, sy2 - sy1, imgSrcWidth, imgDstWidth, true);
} else {
copyRawPixels(imageIn, imageOut, sx1, sy1, dx1, dy1, sx2 - sx1, sy2 - sy1, imgSrcWidth, imgDstWidth, false);
}
}
private static void copyRawPixels(int[] imageIn, byte[] imageOut, int srcX, int srcY, int dstX, int dstY, int width,
int height, int imgSrcWidth, int imgDstWidth, boolean flip) {
int i, j;
for(int y = 0; y < height; ++y) {
for(int x = 0; x < width; ++x) {
i = imageIn[(srcY + y) * imgSrcWidth + srcX + x];
if(flip) {
j = (dstY + y) * imgDstWidth + dstX + width - x - 1;
}else {
j = (dstY + y) * imgDstWidth + dstX + x;
}
j = j << 2;
imageOut[j] = (byte)(i >> 24);
imageOut[j + 1] = (byte)(i & 0xFF);
imageOut[j + 2] = (byte)(i >> 8);
imageOut[j + 3] = (byte)(i >> 16);
}
}
}
}

View File

@ -0,0 +1,120 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.skins;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.md_5.bungee.UserConnection;
/**
* 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 SkinServiceOffline implements ISkinService {
public static final int masterRateLimitPerPlayer = 250;
private static class CachedSkin {
protected final UUID uuid;
protected final byte[] packet;
protected CachedSkin(UUID uuid, byte[] packet) {
this.uuid = uuid;
this.packet = packet;
}
}
private final Map<UUID, CachedSkin> skinCache = new HashMap();
private final Multimap<UUID, UUID> onlinePlayersFromTexturesMap = MultimapBuilder.hashKeys().hashSetValues().build();
public void init(String uri, String driverClass, String driverPath, int keepObjectsDays, int keepProfilesDays,
int maxObjects, int maxProfiles) {
synchronized(skinCache) {
skinCache.clear();
}
}
public void processGetOtherSkin(UUID searchUUID, UserConnection sender) {
if(((EaglerInitialHandler)sender.getPendingConnection()).skinLookupRateLimiter.rateLimit(masterRateLimitPerPlayer)) {
CachedSkin cached;
synchronized(skinCache) {
cached = skinCache.get(searchUUID);
}
if(cached != null) {
sender.sendData(SkinService.CHANNEL, cached.packet);
}else {
sender.sendData(SkinService.CHANNEL, SkinPackets.makePresetResponse(searchUUID));
}
}
}
public void processGetOtherSkin(UUID searchUUID, String skinURL, UserConnection sender) {
Collection<UUID> uuids;
synchronized(onlinePlayersFromTexturesMap) {
uuids = onlinePlayersFromTexturesMap.get(searchUUID);
}
if(uuids.size() > 0) {
CachedSkin cached;
synchronized(skinCache) {
Iterator<UUID> uuidItr = uuids.iterator();
while(uuidItr.hasNext()) {
cached = skinCache.get(uuidItr.next());
if(cached != null) {
sender.sendData(SkinService.CHANNEL, SkinPackets.rewriteUUID(searchUUID, cached.packet));
}
}
}
}
sender.sendData(SkinService.CHANNEL, SkinPackets.makePresetResponse(searchUUID));
}
public void registerEaglercraftPlayer(UUID clientUUID, byte[] generatedPacket, int modelId) throws IOException {
synchronized(skinCache) {
skinCache.put(clientUUID, new CachedSkin(clientUUID, generatedPacket));
}
}
public void unregisterPlayer(UUID clientUUID) {
synchronized(skinCache) {
skinCache.remove(clientUUID);
}
}
public void registerTextureToPlayerAssociation(UUID textureUUID, UUID playerUUID) {
synchronized(onlinePlayersFromTexturesMap) {
onlinePlayersFromTexturesMap.put(textureUUID, playerUUID);
}
}
public void flush() {
// no
}
public void shutdown() {
synchronized(skinCache) {
skinCache.clear();
}
}
}

View File

@ -0,0 +1,121 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.sqlite;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.codehaus.plexus.util.FileUtils;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
/**
* 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 EaglerDrivers {
private static Driver initializeDriver(String address, String driverClass) {
URLClassLoader classLoader = driversJARs.get(address);
if(classLoader == null) {
File driver;
if(address.equalsIgnoreCase("internal")) {
driver = new File(EaglerXBungee.getEagler().getDataFolder(), "drivers/sqlite-jdbc.jar");
driver.getParentFile().mkdirs();
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");
EaglerXBungee.logger().info("Downloading from maven: " + u.toString());
FileUtils.copyURLToFile(u, driver);
} catch (Throwable ex) {
EaglerXBungee.logger().severe("Could not download sqlite-jdbc.jar from repo1.maven.org!");
EaglerXBungee.logger().severe("Please download \"org.xerial:sqlite-jdbc:3.45.0.0\" jar to file: " + driver.getAbsolutePath());
throw new ExceptionInInitializerError(ex);
}
}
}else {
driver = new File(address);
}
URL driverURL;
try {
driverURL = driver.toURI().toURL();
}catch(MalformedURLException ex) {
EaglerXBungee.logger().severe("Invalid JDBC driver path: " + address);
throw new ExceptionInInitializerError(ex);
}
classLoader = new URLClassLoader(new URL[] { driverURL }, ClassLoader.getSystemClassLoader());
driversJARs.put(address, classLoader);
}
Class loadedDriver;
try {
loadedDriver = classLoader.loadClass(driverClass);
}catch(ClassNotFoundException ex) {
try {
classLoader.close();
} catch (IOException e) {
}
EaglerXBungee.logger().severe("Could not find JDBC driver class: " + driverClass);
throw new ExceptionInInitializerError(ex);
}
Driver sqlDriver = null;
try {
sqlDriver = (Driver) loadedDriver.newInstance();
}catch(Throwable ex) {
try {
classLoader.close();
} catch (IOException e) {
}
EaglerXBungee.logger().severe("Could not initialize JDBC driver class: " + driverClass);
throw new ExceptionInInitializerError(ex);
}
return sqlDriver;
}
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 {
if(driverClass.equalsIgnoreCase("internal")) {
driverClass = "org.sqlite.JDBC";
}
if(driverPath == null) {
try {
Class.forName(driverClass);
} catch (ClassNotFoundException e) {
throw new SQLException("Driver class not found in JRE: " + driverClass, e);
}
return DriverManager.getConnection(address, props);
}else {
String driverMapPath = "" + driverPath + "?" + driverClass;
Driver dv = driversDrivers.get(driverMapPath);
if(dv == null) {
dv = initializeDriver(driverPath, driverClass);
driversDrivers.put(driverMapPath, dv);
}
return dv.connect(address, props);
}
}
}

View File

@ -0,0 +1,84 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
/**
* Copyright (c) 2022 ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class ExpiringSet<T> extends HashSet<T> {
private final long expiration;
private final ExpiringEvent<T> event;
private final Map<T, Long> timestamps = new HashMap<>();
public ExpiringSet(long expiration) {
this.expiration = expiration;
this.event = null;
}
public ExpiringSet(long expiration, ExpiringEvent<T> event) {
this.expiration = expiration;
this.event = event;
}
public interface ExpiringEvent<T> {
void onExpiration(T item);
}
public void checkForExpirations() {
Iterator<T> iterator = this.timestamps.keySet().iterator();
long now = System.currentTimeMillis();
while (iterator.hasNext()) {
T element = iterator.next();
if (super.contains(element)) {
if (this.timestamps.get(element) + this.expiration < now) {
if (this.event != null) this.event.onExpiration(element);
iterator.remove();
super.remove(element);
}
} else {
iterator.remove();
super.remove(element);
}
}
}
public boolean add(T o) {
checkForExpirations();
boolean success = super.add(o);
if (success) timestamps.put(o, System.currentTimeMillis());
return success;
}
public boolean remove(Object o) {
checkForExpirations();
boolean success = super.remove(o);
if (success) timestamps.remove(o);
return success;
}
public void clear() {
this.timestamps.clear();
super.clear();
}
public boolean contains(Object o) {
checkForExpirations();
return super.contains(o);
}
}

View File

@ -0,0 +1,243 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.server.EaglerInitialHandler;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.config.ServerInfo;
/**
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class VoiceServerImpl {
private final ServerInfo server;
private final byte[] iceServersPacket;
private final Map<UUID, UserConnection> voicePlayers = new HashMap<>();
private final Map<UUID, ExpiringSet<UUID>> voiceRequests = new HashMap<>();
private final Set<VoicePair> voicePairs = new HashSet<>();
private static final int VOICE_CONNECT_RATELIMIT = 15;
private static class VoicePair {
private final UUID uuid1;
private final UUID uuid2;
@Override
public int hashCode() {
return uuid1.hashCode() ^ uuid2.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
VoicePair other = (VoicePair) obj;
return (uuid1.equals(other.uuid1) && uuid2.equals(other.uuid2))
|| (uuid1.equals(other.uuid2) && uuid2.equals(other.uuid1));
}
private VoicePair(UUID uuid1, UUID uuid2) {
this.uuid1 = uuid1;
this.uuid2 = uuid2;
}
private boolean anyEquals(UUID uuid) {
return uuid1.equals(uuid) || uuid2.equals(uuid);
}
}
VoiceServerImpl(ServerInfo server, byte[] iceServersPacket) {
this.server = server;
this.iceServersPacket = iceServersPacket;
}
public void handlePlayerLoggedIn(UserConnection player) {
player.sendData(VoiceService.CHANNEL, iceServersPacket);
}
public void handlePlayerLoggedOut(UserConnection player) {
removeUser(player.getUniqueId());
}
void handleVoiceSignalPacketTypeRequest(UUID player, UserConnection sender) {
synchronized (voicePlayers) {
UUID senderUUID = sender.getUniqueId();
if (senderUUID.equals(player))
return; // prevent duplicates
if (!voicePlayers.containsKey(senderUUID))
return;
UserConnection targetPlayerCon = voicePlayers.get(player);
if (targetPlayerCon == null)
return;
VoicePair newPair = new VoicePair(player, senderUUID);
if (voicePairs.contains(newPair))
return; // already paired
ExpiringSet<UUID> senderRequestSet = voiceRequests.get(senderUUID);
if (senderRequestSet == null) {
voiceRequests.put(senderUUID, senderRequestSet = new ExpiringSet<>(2000));
}
if (!senderRequestSet.add(player)) {
return;
}
// check if other has requested earlier
ExpiringSet<UUID> theSet;
if ((theSet = voiceRequests.get(player)) != null && theSet.contains(senderUUID)) {
theSet.remove(senderUUID);
if (theSet.isEmpty())
voiceRequests.remove(player);
senderRequestSet.remove(player);
if (senderRequestSet.isEmpty())
voiceRequests.remove(senderUUID);
// send each other add data
voicePairs.add(newPair);
targetPlayerCon.sendData(VoiceService.CHANNEL,
VoiceSignalPackets.makeVoiceSignalPacketConnect(senderUUID, false));
sender.sendData(VoiceService.CHANNEL, VoiceSignalPackets.makeVoiceSignalPacketConnect(player, true));
}
}
}
void handleVoiceSignalPacketTypeConnect(UserConnection sender) {
if(!((EaglerInitialHandler)sender.getPendingConnection()).voiceConnectRateLimiter.rateLimit(VOICE_CONNECT_RATELIMIT)) {
return;
}
synchronized (voicePlayers) {
if (voicePlayers.containsKey(sender.getUniqueId())) {
return;
}
boolean hasNoOtherPlayers = voicePlayers.isEmpty();
voicePlayers.put(sender.getUniqueId(), sender);
if (hasNoOtherPlayers) {
return;
}
byte[] packetToBroadcast = VoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values());
for (UserConnection userCon : voicePlayers.values()) {
userCon.sendData(VoiceService.CHANNEL, packetToBroadcast);
}
}
}
void handleVoiceSignalPacketTypeICE(UUID player, String str, UserConnection sender) {
UserConnection pass;
VoicePair pair = new VoicePair(player, sender.getUniqueId());
synchronized (voicePlayers) {
pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null;
}
if (pass != null) {
pass.sendData(VoiceService.CHANNEL, VoiceSignalPackets.makeVoiceSignalPacketICE(sender.getUniqueId(), str));
}
}
void handleVoiceSignalPacketTypeDesc(UUID player, String str, UserConnection sender) {
UserConnection pass;
VoicePair pair = new VoicePair(player, sender.getUniqueId());
synchronized (voicePlayers) {
pass = voicePairs.contains(pair) ? voicePlayers.get(player) : null;
}
if (pass != null) {
pass.sendData(VoiceService.CHANNEL,
VoiceSignalPackets.makeVoiceSignalPacketDesc(sender.getUniqueId(), str));
}
}
void handleVoiceSignalPacketTypeDisconnect(UUID player, UserConnection sender) {
if (player != null) {
synchronized (voicePlayers) {
if (!voicePlayers.containsKey(player)) {
return;
}
byte[] userDisconnectPacket = null;
Iterator<VoicePair> pairsItr = voicePairs.iterator();
while (pairsItr.hasNext()) {
VoicePair voicePair = pairsItr.next();
UUID target = null;
if (voicePair.uuid1.equals(player)) {
target = voicePair.uuid2;
} else if (voicePair.uuid2.equals(player)) {
target = voicePair.uuid1;
}
if (target != null) {
pairsItr.remove();
UserConnection conn = voicePlayers.get(target);
if (conn != null) {
if (userDisconnectPacket == null) {
userDisconnectPacket = VoiceSignalPackets.makeVoiceSignalPacketDisconnect(player);
}
conn.sendData(VoiceService.CHANNEL, userDisconnectPacket);
}
sender.sendData(VoiceService.CHANNEL,
VoiceSignalPackets.makeVoiceSignalPacketDisconnect(target));
}
}
}
} else {
removeUser(sender.getUniqueId());
}
}
public void removeUser(UUID user) {
synchronized (voicePlayers) {
if (voicePlayers.remove(user) == null) {
return;
}
voiceRequests.remove(user);
if (voicePlayers.size() > 0) {
byte[] voicePlayersPkt = VoiceSignalPackets.makeVoiceSignalPacketGlobal(voicePlayers.values());
for (UserConnection userCon : voicePlayers.values()) {
if (!user.equals(userCon.getUniqueId())) {
userCon.sendData(VoiceService.CHANNEL, voicePlayersPkt);
}
}
}
byte[] userDisconnectPacket = null;
Iterator<VoicePair> pairsItr = voicePairs.iterator();
while (pairsItr.hasNext()) {
VoicePair voicePair = pairsItr.next();
UUID target = null;
if (voicePair.uuid1.equals(user)) {
target = voicePair.uuid2;
} else if (voicePair.uuid2.equals(user)) {
target = voicePair.uuid1;
}
if (target != null) {
pairsItr.remove();
if (voicePlayers.size() > 0) {
UserConnection conn = voicePlayers.get(target);
if (conn != null) {
if (userDisconnectPacket == null) {
userDisconnectPacket = VoiceSignalPackets.makeVoiceSignalPacketDisconnect(user);
}
conn.sendData(VoiceService.CHANNEL, userDisconnectPacket);
}
}
}
}
}
}
}

View File

@ -0,0 +1,118 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import gnu.trove.map.TMap;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.config.ServerInfo;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class VoiceService {
public static final String CHANNEL = "EAG|Voice-1.8";
private final Map<String, VoiceServerImpl> serverMap = new HashMap();
private final byte[] disableVoicePacket;
public VoiceService(EaglerBungeeConfig conf) {
this.disableVoicePacket = VoiceSignalPackets.makeVoiceSignalPacketAllowed(false, null);
String[] iceServers = conf.getICEServers().toArray(new String[conf.getICEServers().size()]);
byte[] iceServersPacket = VoiceSignalPackets.makeVoiceSignalPacketAllowed(true, iceServers);
TMap<String,ServerInfo> servers = BungeeCord.getInstance().config.getServers();
Set<String> keySet = new HashSet(servers.keySet());
keySet.removeAll(conf.getDisableVoiceOnServersSet());
for(String s : keySet) {
serverMap.put(s, new VoiceServerImpl(servers.get(s), iceServersPacket));
}
}
public void handlePlayerLoggedIn(UserConnection player) {
}
public void handlePlayerLoggedOut(UserConnection player) {
}
public void handleServerConnected(UserConnection player, ServerInfo server) {
VoiceServerImpl svr = serverMap.get(server.getName());
if(svr != null) {
svr.handlePlayerLoggedIn(player);
}else {
player.sendData(CHANNEL, disableVoicePacket);
}
}
public void handleServerDisconnected(UserConnection player, ServerInfo server) {
VoiceServerImpl svr = serverMap.get(server.getName());
if(svr != null) {
svr.handlePlayerLoggedOut(player);
}
}
void handleVoiceSignalPacketTypeRequest(UUID player, UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
svr.handleVoiceSignalPacketTypeRequest(player, sender);
}
}
}
void handleVoiceSignalPacketTypeConnect(UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
svr.handleVoiceSignalPacketTypeConnect(sender);
}
}
}
void handleVoiceSignalPacketTypeICE(UUID player, String str, UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
svr.handleVoiceSignalPacketTypeICE(player, str, sender);
}
}
}
void handleVoiceSignalPacketTypeDesc(UUID player, String str, UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
svr.handleVoiceSignalPacketTypeDesc(player, str, sender);
}
}
}
void handleVoiceSignalPacketTypeDisconnect(UUID player, UserConnection sender) {
if(sender.getServer() != null) {
VoiceServerImpl svr = serverMap.get(sender.getServer().getInfo().getName());
if(svr != null) {
svr.handleVoiceSignalPacketTypeDisconnect(player, sender);
}
}
}
}

View File

@ -0,0 +1,194 @@
package net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.voice;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.UUID;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.DefinedPacket;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class VoiceSignalPackets {
static final int VOICE_SIGNAL_ALLOWED = 0;
static final int VOICE_SIGNAL_REQUEST = 0;
static final int VOICE_SIGNAL_CONNECT = 1;
static final int VOICE_SIGNAL_DISCONNECT = 2;
static final int VOICE_SIGNAL_ICE = 3;
static final int VOICE_SIGNAL_DESC = 4;
static final int VOICE_SIGNAL_GLOBAL = 5;
public static void processPacket(byte[] data, UserConnection sender, VoiceService voiceService) throws IOException {
int packetId = -1;
if(data.length == 0) {
throw new IOException("Zero-length packet recieved");
}
try {
ByteBuf buffer = Unpooled.wrappedBuffer(data).writerIndex(data.length);
packetId = buffer.readUnsignedByte();
switch(packetId) {
case VOICE_SIGNAL_REQUEST: {
voiceService.handleVoiceSignalPacketTypeRequest(DefinedPacket.readUUID(buffer), sender);
break;
}
case VOICE_SIGNAL_CONNECT: {
voiceService.handleVoiceSignalPacketTypeConnect(sender);
break;
}
case VOICE_SIGNAL_ICE: {
voiceService.handleVoiceSignalPacketTypeICE(DefinedPacket.readUUID(buffer), DefinedPacket.readString(buffer, 32767), sender);
break;
}
case VOICE_SIGNAL_DESC: {
voiceService.handleVoiceSignalPacketTypeDesc(DefinedPacket.readUUID(buffer), DefinedPacket.readString(buffer, 32767), sender);
break;
}
case VOICE_SIGNAL_DISCONNECT: {
voiceService.handleVoiceSignalPacketTypeDisconnect(buffer.readableBytes() > 0 ? DefinedPacket.readUUID(buffer) : null, sender);
break;
}
default: {
throw new IOException("Unknown packet type " + packetId);
}
}
if(buffer.readableBytes() > 0) {
throw new IOException("Voice packet is too long!");
}
}catch(IOException ex) {
throw ex;
}catch(Throwable t) {
throw new IOException("Unhandled exception handling voice packet type " + packetId, t);
}
}
static byte[] makeVoiceSignalPacketAllowed(boolean allowed, String[] iceServers) {
if (iceServers == null) {
byte[] ret = new byte[2];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_ALLOWED);
wrappedBuffer.writeBoolean(allowed);
return ret;
}
byte[][] iceServersBytes = new byte[iceServers.length][];
int totalLen = 2 + getVarIntSize(iceServers.length);
for(int i = 0; i < iceServers.length; ++i) {
byte[] b = iceServersBytes[i] = iceServers[i].getBytes(StandardCharsets.UTF_8);
totalLen += getVarIntSize(b.length) + b.length;
}
byte[] ret = new byte[totalLen];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_ALLOWED);
wrappedBuffer.writeBoolean(allowed);
DefinedPacket.writeVarInt(iceServersBytes.length, wrappedBuffer);
for(int i = 0; i < iceServersBytes.length; ++i) {
byte[] b = iceServersBytes[i];
DefinedPacket.writeVarInt(b.length, wrappedBuffer);
wrappedBuffer.writeBytes(b);
}
return ret;
}
static byte[] makeVoiceSignalPacketGlobal(Collection<UserConnection> users) {
int cnt = users.size();
byte[][] displayNames = new byte[cnt][];
int i = 0;
for(UserConnection user : users) {
String name = user.getDisplayName();
if(name.length() > 16) name = name.substring(0, 16);
displayNames[i++] = name.getBytes(StandardCharsets.UTF_8);
}
int totalLength = 1 + getVarIntSize(cnt) + (cnt << 4);
for(i = 0; i < cnt; ++i) {
totalLength += getVarIntSize(displayNames[i].length) + displayNames[i].length;
}
byte[] ret = new byte[totalLength];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_GLOBAL);
DefinedPacket.writeVarInt(cnt, wrappedBuffer);
for(UserConnection user : users) {
DefinedPacket.writeUUID(user.getUniqueId(), wrappedBuffer);
}
for(i = 0; i < cnt; ++i) {
DefinedPacket.writeVarInt(displayNames[i].length, wrappedBuffer);
wrappedBuffer.writeBytes(displayNames[i]);
}
return ret;
}
static byte[] makeVoiceSignalPacketConnect(UUID player, boolean offer) {
byte[] ret = new byte[18];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_CONNECT);
DefinedPacket.writeUUID(player, wrappedBuffer);
wrappedBuffer.writeBoolean(offer);
return ret;
}
static byte[] makeVoiceSignalPacketConnectAnnounce(UUID player) {
byte[] ret = new byte[17];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_CONNECT);
DefinedPacket.writeUUID(player, wrappedBuffer);
return ret;
}
static byte[] makeVoiceSignalPacketDisconnect(UUID player) {
if(player == null) {
return new byte[] { (byte)VOICE_SIGNAL_DISCONNECT };
}
byte[] ret = new byte[17];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_DISCONNECT);
DefinedPacket.writeUUID(player, wrappedBuffer);
return ret;
}
static byte[] makeVoiceSignalPacketICE(UUID player, String str) {
byte[] strBytes = str.getBytes(StandardCharsets.UTF_8);
byte[] ret = new byte[17 + getVarIntSize(strBytes.length) + strBytes.length];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_ICE);
DefinedPacket.writeUUID(player, wrappedBuffer);
DefinedPacket.writeVarInt(strBytes.length, wrappedBuffer);
wrappedBuffer.writeBytes(strBytes);
return ret;
}
static byte[] makeVoiceSignalPacketDesc(UUID player, String str) {
byte[] strBytes = str.getBytes(StandardCharsets.UTF_8);
byte[] ret = new byte[17 + getVarIntSize(strBytes.length) + strBytes.length];
ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(ret).writerIndex(0);
wrappedBuffer.writeByte(VOICE_SIGNAL_DESC);
DefinedPacket.writeUUID(player, wrappedBuffer);
DefinedPacket.writeVarInt(strBytes.length, wrappedBuffer);
wrappedBuffer.writeBytes(strBytes);
return ret;
}
public static int getVarIntSize(int input) {
for (int i = 1; i < 5; ++i) {
if ((input & -1 << i * 7) == 0) {
return i;
}
}
return 5;
}
}

View File

@ -0,0 +1,17 @@
enable_authentication_system: true
use_onboard_eaglerx_system: true
auth_db_uri: 'jdbc:sqlite:eaglercraft_auths.db'
sql_driver_class: 'internal'
sql_driver_path: 'internal'
password_prompt_screen_text: 'Enter your password to join:'
wrong_password_screen_text: 'Password Incorrect!'
not_registered_screen_text: 'You are not registered on this server!'
eagler_command_name: 'eagler'
use_register_command_text: '&aUse /eagler to set an Eaglercraft password on this account'
use_change_command_text: '&bUse /eagler to change your Eaglercraft password'
command_success_text: '&bYour eagler password was changed successfully.'
last_eagler_login_message: 'Your last Eaglercraft login was on $date from $ip'
too_many_registrations_message: '&cThe maximum number of registrations has been reached for your IP address'
need_vanilla_to_register_message: '&cYou need to log in with a vanilla account to use this command'
override_eagler_to_vanilla_skins: false
max_registration_per_ip: -1

View File

@ -0,0 +1,180 @@
{
"text/html": {
"files": [ "html", "htm", "shtml" ],
"expires": 3600,
"charset": "utf-8"
},
"application/javascript": {
"files": [ "js" ],
"expires": 3600,
"charset": "utf-8"
},
"application/octet-stream": {
"files": [ "epk" ],
"expires": 14400
},
"text/css": {
"files": [ "css" ],
"expires": 14400,
"charset": "utf-8"
},
"text/xml": {
"files": [ "xml" ],
"expires": 3600,
"charset": "utf-8"
},
"text/plain": {
"files": [ "txt" ],
"expires": 3600,
"charset": "utf-8"
},
"image/png": {
"files": [ "png" ],
"expires": 14400
},
"image/jpeg": {
"files": [ "jpeg", "jpg", "jfif" ],
"expires": 14400
},
"image/gif": {
"files": [ "gif" ],
"expires": 14400
},
"image/webp": {
"files": [ "webp" ],
"expires": 14400
},
"image/svg+xml": {
"files": [ "svg", "svgz" ],
"expires": 14400,
"charset": "utf-8"
},
"image/tiff": {
"files": [ "tiff", "tif" ],
"expires": 14400
},
"image/avif": {
"files": [ "avif" ],
"expires": 14400
},
"image/x-ms-bmp": {
"files": [ "bmp" ],
"expires": 14400
},
"image/x-icon": {
"files": [ "ico" ],
"expires": 14400
},
"image/woff": {
"files": [ "woff" ],
"expires": 43200
},
"image/woff2": {
"files": [ "woff2" ],
"expires": 43200
},
"application/json": {
"files": [ "json" ],
"expires": 3600,
"charset": "utf-8"
},
"application/pdf": {
"files": [ "pdf" ],
"expires": 14400
},
"application/rtf": {
"files": [ "rtf" ],
"expires": 14400
},
"application/java-archive": {
"files": [ "jar", "war", "ear" ],
"expires": 14400
},
"application/wasm": {
"files": [ "wasm" ],
"expires": 3600
},
"application/xhtml+xml": {
"files": [ "xhtml" ],
"expires": 3600,
"charset": "utf-8"
},
"application/zip": {
"files": [ "zip" ],
"expires": 14400
},
"audio/midi": {
"files": [ "mid", "midi", "kar" ],
"expires": 43200
},
"audio/mpeg": {
"files": [ "mp3" ],
"expires": 43200
},
"audio/ogg": {
"files": [ "ogg" ],
"expires": 43200
},
"audio/x-m4a": {
"files": [ "m4a" ],
"expires": 43200
},
"application/atom+xml": {
"files": [ "atom" ],
"expires": 3600,
"charset": "utf-8"
},
"application/rss+xml": {
"files": [ "rss" ],
"expires": 3600,
"charset": "utf-8"
},
"application/x-shockwave-flash": {
"files": [ "swf" ],
"expires": 43200
},
"video/3gpp": {
"files": [ "3gpp", "3gp" ],
"expires": 43200
},
"video/mp4": {
"files": [ "mp4" ],
"expires": 43200
},
"video/mpeg": {
"files": [ "mpeg", "mpg" ],
"expires": 43200
},
"video/quicktime": {
"files": [ "mov" ],
"expires": 43200
},
"video/webm": {
"files": [ "webm" ],
"expires": 43200
},
"video/x-motion-jpeg": {
"files": [ "mjpg" ],
"expires": 14400
},
"video/x-flv": {
"files": [ "flv" ],
"expires": 43200
},
"video/x-m4v": {
"files": [ "m4v" ],
"expires": 43200
},
"video/x-mng": {
"files": [ "3mng" ],
"expires": 43200
},
"video/x-ms-wmv": {
"files": [ "wmv" ],
"expires": 43200
},
"video/x-msvideo": {
"files": [ "avi" ],
"expires": 43200
}
}

View File

@ -0,0 +1,20 @@
voice_stun_servers:
- 'stun:stun.l.google.com:19302'
- 'stun:stun1.l.google.com:19302'
- 'stun:stun2.l.google.com:19302'
- 'stun:stun3.l.google.com:19302'
- 'stun:stun4.l.google.com:19302'
- 'stun:openrelay.metered.ca:80'
voice_turn_servers:
openrelay1:
url: 'turn:openrelay.metered.ca:80'
username: 'openrelayproject'
password: 'openrelayproject'
openrelay2:
url: 'turn:openrelay.metered.ca:443'
username: 'openrelayproject'
password: 'openrelayproject'
openrelay3:
url: 'turn:openrelay.metered.ca:443?transport=tcp'
username: 'openrelayproject'
password: 'openrelayproject'

View File

@ -0,0 +1,66 @@
listener_01:
address: 0.0.0.0:8081
address_v6: 'null'
max_players: 60
tab_list: GLOBAL_PING
default_server: lobby
force_default_server: false
forward_ip: false
forward_ip_header: X-Real-IP
redirect_legacy_clients_to: 'null'
server_icon: server-icon.png
server_motd:
- '&6An EaglercraftX server'
allow_motd: true
allow_query: true
request_motd_cache:
cache_ttl: 7200
online_server_list_animation: false
online_server_list_results: true
online_server_list_trending: true
online_server_list_portfolios: false
http_server:
enabled: false
root: 'web'
page_404_not_found: 'default'
page_index_name:
- 'index.html'
- 'index.htm'
allow_voice: false
ratelimit:
ip:
enable: true
period: 90
limit: 60
limit_lockout: 80
lockout_duration: 1200
exceptions:
- '127.*'
- '0:0:0:0:0:0:0:1'
login:
enable: true
period: 50
limit: 5
limit_lockout: 10
lockout_duration: 300
exceptions:
- '127.*'
- '0:0:0:0:0:0:0:1'
motd:
enable: true
period: 30
limit: 5
limit_lockout: 15
lockout_duration: 300
exceptions:
- '127.*'
- '0:0:0:0:0:0:0:1'
query:
enable: true
period: 30
limit: 15
limit_lockout: 25
lockout_duration: 900
exceptions:
- '127.*'
- '0:0:0:0:0:0:0:1'

View File

@ -0,0 +1,25 @@
server_name: 'EaglercraftXBungee Server'
server_uuid: ${random_uuid}
websocket_connection_timeout: 15000
websocket_handshake_timeout: 5000
http_websocket_compression_level: 6
download_vanilla_skins_to_clients: true
valid_skin_download_urls:
- 'textures.minecraft.net'
uuid_lookup_ratelimit_player: 50
uuid_lookup_ratelimit_global: 175
skin_download_ratelimit_player: 1000
skin_download_ratelimit_global: 30000
skin_cache_db_uri: 'jdbc:sqlite:eaglercraft_skins_cache.db'
skin_cache_keep_objects_days: 45
skin_cache_keep_profiles_days: 7
skin_cache_max_objects: 32768
skin_cache_max_profiles: 32768
skin_cache_antagonists_ratelimit: 15
sql_driver_class: 'internal'
sql_driver_path: 'internal'
eagler_players_vanilla_skin: ''
enable_is_eagler_player_property: true
disable_voice_chat_on_servers: []
disable_fnaw_skins_everywhere: false
disable_fnaw_skins_on_servers: []

View File

@ -0,0 +1,9 @@
block_all_client_updates: false
discard_login_packet_certs: false
cert_packet_data_rate_limit: 524288
enable_eagcert_folder: true
download_latest_certs: true
download_certs_from:
- 'https://eaglercraft.com/backup.cert'
- 'https://deev.is/eagler/backup.cert'
check_for_update_every: 900

View File

@ -0,0 +1,5 @@
name: EaglercraftXBungee
main: net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee
version: 1.1.0
description: Plugin to allow EaglercraftX 1.8 players to join your network, or allow EaglercraftX 1.8 players to use your network as a proxy to join other networks
author: lax1dude