Initial Commit
This commit is contained in:
commit
06f03a918c
|
@ -0,0 +1,4 @@
|
||||||
|
lib/*
|
||||||
|
.idea/*
|
||||||
|
*.iml
|
||||||
|
out/*
|
Binary file not shown.
|
@ -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
|
|
@ -0,0 +1,4 @@
|
||||||
|
Manifest-Version: 1.0
|
||||||
|
Main-Class: net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.shit
|
||||||
|
.MainClass
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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 };
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 };
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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("<", "<").replace(">", ">") + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) {
|
||||||
|
EaglerPipeline.closeChannel(ctx.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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("<", "<").replace(">", ">");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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'
|
|
@ -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'
|
|
@ -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: []
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue