diff --git a/eaglerbungee/.gitignore b/eaglerbungee/.gitignore new file mode 100644 index 0000000..0fde6ec --- /dev/null +++ b/eaglerbungee/.gitignore @@ -0,0 +1,37 @@ +# Eclipse stuff +.classpath +.project +.settings/ + +# netbeans +nbproject/ +nbactions.xml + +# we use maven! +build.xml + +# maven +target/ +dependency-reduced-pom.xml + +# vim +.*.sw[a-p] + +# various other potential build files +build/ +bin/ +dist/ +manifest.mf + +# Mac filesystem dust +.DS_Store/ + +# intellij +*.iml +*.ipr +*.iws +.idea/ + +# other files +*.log* +*.yml diff --git a/eaglerbungee/Java-WebSocket-1.5.1-with-dependencies.jar b/eaglerbungee/Java-WebSocket-1.5.1-with-dependencies.jar new file mode 100644 index 0000000..8f103a7 Binary files /dev/null and b/eaglerbungee/Java-WebSocket-1.5.1-with-dependencies.jar differ diff --git a/eaglerbungee/build.gradle b/eaglerbungee/build.gradle new file mode 100644 index 0000000..e724149 --- /dev/null +++ b/eaglerbungee/build.gradle @@ -0,0 +1,39 @@ +plugins { + id "java" +} + +sourceSets { + main { + java { + srcDirs( + "src/main/java" + ) + } + } +} + +repositories { + mavenCentral() +} + +tasks.withType(JavaCompile) { + options.warnings = false + options.compilerArgs << "-Xmaxerrs" << "1000" +} + +dependencies { + implementation fileTree(dir: '.', include: '*.jar') +} + +jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + manifest { + attributes( + 'Main-Class': 'net.md_5.bungee.BungeeCord' + ) + } + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } +} \ No newline at end of file diff --git a/eaglerbungee/bungee-deps.jar b/eaglerbungee/bungee-deps.jar new file mode 100644 index 0000000..36b6759 Binary files /dev/null and b/eaglerbungee/bungee-deps.jar differ diff --git a/eaglerbungee/gradle.properties b/eaglerbungee/gradle.properties new file mode 100644 index 0000000..2913ef0 --- /dev/null +++ b/eaglerbungee/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx2G -Xms2G + diff --git a/eaglerbungee/gradle/wrapper/gradle-wrapper.jar b/eaglerbungee/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/eaglerbungee/gradle/wrapper/gradle-wrapper.jar differ diff --git a/eaglerbungee/gradle/wrapper/gradle-wrapper.properties b/eaglerbungee/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e9da0e8 --- /dev/null +++ b/eaglerbungee/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Feb 11 10:50:57 EST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/eaglerbungee/gradlew b/eaglerbungee/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/eaglerbungee/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/eaglerbungee/gradlew.bat b/eaglerbungee/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/eaglerbungee/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/eaglerbungee/nb-configuration.xml b/eaglerbungee/nb-configuration.xml new file mode 100644 index 0000000..7e46592 --- /dev/null +++ b/eaglerbungee/nb-configuration.xml @@ -0,0 +1,31 @@ + + + + + + project + NEW_LINE + NEW_LINE + NEW_LINE + true + true + true + true + true + true + true + true + true + true + + diff --git a/eaglerbungee/pom.xml b/eaglerbungee/pom.xml new file mode 100644 index 0000000..768bda2 --- /dev/null +++ b/eaglerbungee/pom.xml @@ -0,0 +1,114 @@ + + + 4.0.0 + + + org.sonatype.oss + oss-parent + 7 + + + net.md-5 + bungeecord-parent + 1.6.4-SNAPSHOT + pom + + BungeeCord + Parent project for all BungeeCord modules. + https://github.com/SpigotMC/BungeeCord + 2012 + + Elastic Portal Suite + https://github.com/SpigotMC + + + + The BSD 3-Clause License + http://opensource.org/licenses/BSD-3-Clause + repo + + + + + + md_5 + + + + + api + bootstrap + config + event + protocol + proxy + query + + + + scm:git:git@github.com:SpigotMC/BungeeCord.git + scm:git:git@github.com:SpigotMC/BungeeCord.git + git@github.com:SpigotMC/BungeeCord.git + + + GitHub + https://github.com/SpigotMC/BungeeCord/issues + + + jenkins + http://ci.md-5.net/job/BungeeCord + + + + unknown + 4.0.9.Final + UTF-8 + + + + + junit + junit + 4.11 + test + + + org.projectlombok + lombok + 1.12.2 + provided + + + + + + + com.lukegb.mojo + gitdescribe-maven-plugin + 1.3 + + git-${project.name}-${project.version}- + -${build.number} + + + + compile + + gitdescribe + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5.1 + + 1.7 + 1.7 + + + + + diff --git a/eaglerbungee/settings.gradle b/eaglerbungee/settings.gradle new file mode 100644 index 0000000..66b966e --- /dev/null +++ b/eaglerbungee/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'bungee-dist' diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/Bootstrap.java b/eaglerbungee/src/main/java/net/md_5/bungee/Bootstrap.java new file mode 100644 index 0000000..aa43426 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/Bootstrap.java @@ -0,0 +1,44 @@ +package net.md_5.bungee; + +import java.util.Arrays; +import java.util.List; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.command.ConsoleCommandSender; + +public class Bootstrap +{ + + private static List list(String... params) + { + return Arrays.asList( params ); + } + + /** + * Starts a new instance of BungeeCord. + * + * @param args command line arguments, currently none are used + * @throws Exception when the server cannot be started + */ + public static void main(String[] args) throws Exception + { + System.setProperty( "java.net.preferIPv4Stack", "true" ); + + BungeeCord bungee = new BungeeCord(); + ProxyServer.setInstance( bungee ); + bungee.getLogger().info( "Enabled BungeeCord version " + bungee.getVersion() ); + bungee.start(); + + while ( bungee.isRunning ) + { + String line = bungee.getConsoleReader().readLine( ">" ); + if ( line != null ) + { + if ( !bungee.getPluginManager().dispatchCommand( ConsoleCommandSender.getInstance(), line ) ) + { + bungee.getConsole().sendMessage( ChatColor.RED + "Command not found" ); + } + } + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/BungeeCord.java b/eaglerbungee/src/main/java/net/md_5/bungee/BungeeCord.java new file mode 100644 index 0000000..e9f2763 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/BungeeCord.java @@ -0,0 +1,574 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.SocketAddress; +import java.util.*; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.fusesource.jansi.AnsiConsole; + +import com.google.common.io.ByteStreams; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandler; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.MultithreadEventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.util.AttributeKey; +import io.netty.util.concurrent.GenericFutureListener; +import jline.UnsupportedTerminal; +import jline.console.ConsoleReader; +import jline.internal.Log; +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.ReconnectHandler; +import net.md_5.bungee.api.config.ConfigurationAdapter; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.plugin.PluginManager; +import net.md_5.bungee.api.scheduler.TaskScheduler; +import net.md_5.bungee.api.tab.CustomTabList; +import net.md_5.bungee.command.CommandAlert; +import net.md_5.bungee.command.CommandBungee; +import net.md_5.bungee.command.CommandChangePassword; +import net.md_5.bungee.command.CommandClearRatelimit; +import net.md_5.bungee.command.CommandConfirmCode; +import net.md_5.bungee.command.CommandDomain; +import net.md_5.bungee.command.CommandDomainBlock; +import net.md_5.bungee.command.CommandDomainBlockDomain; +import net.md_5.bungee.command.CommandDomainUnblock; +import net.md_5.bungee.command.CommandEnd; +import net.md_5.bungee.command.CommandFind; +import net.md_5.bungee.command.CommandGlobalBan; +import net.md_5.bungee.command.CommandGlobalBanIP; +import net.md_5.bungee.command.CommandGlobalBanRegex; +import net.md_5.bungee.command.CommandGlobalBanReload; +import net.md_5.bungee.command.CommandGlobalBanWildcard; +import net.md_5.bungee.command.CommandGlobalCheckBan; +import net.md_5.bungee.command.CommandGlobalListBan; +import net.md_5.bungee.command.CommandGlobalUnban; +import net.md_5.bungee.command.CommandIP; +import net.md_5.bungee.command.CommandList; +import net.md_5.bungee.command.CommandPerms; +import net.md_5.bungee.command.CommandReload; +import net.md_5.bungee.command.CommandSend; +import net.md_5.bungee.command.CommandServer; +import net.md_5.bungee.command.ConsoleCommandSender; +import net.md_5.bungee.config.Configuration; +import net.md_5.bungee.config.YamlConfig; +import net.md_5.bungee.eaglercraft.AuthHandler; +import net.md_5.bungee.eaglercraft.AuthSystem; +import net.md_5.bungee.eaglercraft.BanList; +import net.md_5.bungee.eaglercraft.DomainBlacklist; +import net.md_5.bungee.eaglercraft.PluginEaglerSkins; +import net.md_5.bungee.eaglercraft.PluginEaglerVoice; +import net.md_5.bungee.eaglercraft.WebSocketListener; +import net.md_5.bungee.log.BungeeLogger; +import net.md_5.bungee.log.LoggingOutputStream; +import net.md_5.bungee.netty.PipelineUtils; +import net.md_5.bungee.protocol.packet.DefinedPacket; +import net.md_5.bungee.protocol.packet.Packet3Chat; +import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; +import net.md_5.bungee.reconnect.SQLReconnectHandler; +import net.md_5.bungee.scheduler.BungeeScheduler; +import net.md_5.bungee.scheduler.BungeeThreadPool; +import net.md_5.bungee.tab.Custom; +import net.md_5.bungee.util.CaseInsensitiveMap; + +public class BungeeCord extends ProxyServer { + public volatile boolean isRunning; + public final Configuration config; + public final ResourceBundle bundle; + public final ScheduledThreadPoolExecutor executors; + public final MultithreadEventLoopGroup eventLoops; + private final Timer saveThread; + private final Timer reloadBanThread; + private final Timer closeInactiveSockets; + private final Timer authTimeoutTimer; + private Collection listeners; + private Collection wsListeners; + private final Map connections; + private final ReadWriteLock connectionLock; + public final PluginManager pluginManager; + private ReconnectHandler reconnectHandler; + private ConfigurationAdapter configurationAdapter; + private final Collection pluginChannels; + private final File pluginsFolder; + private final TaskScheduler scheduler; + private ConsoleReader consoleReader; + private final Logger logger; + private Collection banCommands; + public AuthSystem authSystem; + public String tokenVerify; + + public static BungeeCord getInstance() { + return (BungeeCord) ProxyServer.getInstance(); + } + + public BungeeCord() throws IOException { + this.config = new Configuration(); + this.bundle = ResourceBundle.getBundle("messages_en"); + this.executors = new BungeeThreadPool(new ThreadFactoryBuilder().setNameFormat("Bungee Pool Thread #%1$d").build()); + this.eventLoops = (MultithreadEventLoopGroup) new NioEventLoopGroup(Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder().setNameFormat("Netty IO Thread #%1$d").build()); + this.saveThread = new Timer("Reconnect Saver"); + this.reloadBanThread = new Timer("Ban List Reload"); + this.closeInactiveSockets = new Timer("Close Inactive WebSockets"); + this.authTimeoutTimer = new Timer("Auth Timeout"); + this.listeners = new HashSet(); + this.wsListeners = new HashSet(); + this.connections = (Map) new CaseInsensitiveMap(); + this.connectionLock = new ReentrantReadWriteLock(); + this.pluginManager = new PluginManager(this); + this.configurationAdapter = new YamlConfig(); + this.pluginChannels = new HashSet(); + this.pluginsFolder = new File("plugins"); + this.scheduler = new BungeeScheduler(); + this.banCommands = new ArrayList(); + this.getPluginManager().registerCommand(null, new CommandReload()); + this.getPluginManager().registerCommand(null, new CommandEnd()); + this.getPluginManager().registerCommand(null, new CommandList()); + this.getPluginManager().registerCommand(null, new CommandServer()); + this.getPluginManager().registerCommand(null, new CommandIP()); + this.getPluginManager().registerCommand(null, new CommandAlert()); + this.getPluginManager().registerCommand(null, new CommandBungee()); + this.getPluginManager().registerCommand(null, new CommandPerms()); + this.getPluginManager().registerCommand(null, new CommandSend()); + this.getPluginManager().registerCommand(null, new CommandFind()); + this.getPluginManager().registerCommand(null, new CommandClearRatelimit()); + this.getPluginManager().registerCommand(null, new CommandConfirmCode()); + this.getPluginManager().registerCommand(null, new CommandDomain()); + this.getPluginManager().registerCommand(null, new CommandDomainBlock()); + this.getPluginManager().registerCommand(null, new CommandDomainBlockDomain()); + this.getPluginManager().registerCommand(null, new CommandDomainUnblock()); + this.registerChannel("BungeeCord"); + Log.setOutput(new PrintStream(ByteStreams.nullOutputStream())); + AnsiConsole.systemInstall(); + this.consoleReader = new ConsoleReader(); + this.logger = new BungeeLogger(this); + System.setErr(new PrintStream(new LoggingOutputStream(this.logger, Level.SEVERE), true)); + System.setOut(new PrintStream(new LoggingOutputStream(this.logger, Level.INFO), true)); + if (this.consoleReader.getTerminal() instanceof UnsupportedTerminal) { + this.logger.info("Unable to initialize fancy terminal. To fix this on Windows, install the correct Microsoft Visual C++ 2008 Runtime"); + this.logger.info("NOTE: This error is non crucial, and BungeeCord will still function correctly! Do not bug the author about it unless you are still unable to get it working"); + } + } + + public void reconfigureBanCommands(boolean replaceBukkit) { + if(banCommands.size() > 0) { + for(Command c : banCommands) { + this.getPluginManager().unregisterCommand(c); + } + banCommands.clear(); + } + + Command cBan = new CommandGlobalBan(replaceBukkit); + Command cUnban = new CommandGlobalUnban(replaceBukkit); + Command cBanReload = new CommandGlobalBanReload(replaceBukkit); + Command cBanIP = new CommandGlobalBanIP(replaceBukkit); + Command cBanWildcard = new CommandGlobalBanWildcard(replaceBukkit); + Command cBanRegex = new CommandGlobalBanRegex(replaceBukkit); + Command cBanCheck = new CommandGlobalCheckBan(replaceBukkit); + Command cBanList = new CommandGlobalListBan(replaceBukkit); + + banCommands.add(cBan); + banCommands.add(cUnban); + banCommands.add(cBanReload); + banCommands.add(cBanIP); + banCommands.add(cBanWildcard); + banCommands.add(cBanRegex); + banCommands.add(cBanCheck); + banCommands.add(cBanList); + + this.getPluginManager().registerCommand(null, cBan); + this.getPluginManager().registerCommand(null, cUnban); + this.getPluginManager().registerCommand(null, cBanReload); + this.getPluginManager().registerCommand(null, cBanIP); + this.getPluginManager().registerCommand(null, cBanWildcard); + this.getPluginManager().registerCommand(null, cBanRegex); + this.getPluginManager().registerCommand(null, cBanCheck); + this.getPluginManager().registerCommand(null, cBanList); + } + + public static void main(final String[] args) throws Exception { + final BungeeCord bungee = new BungeeCord(); + ProxyServer.setInstance(bungee); + bungee.getLogger().info("Enabled BungeeCord version " + bungee.getVersion()); + bungee.start(); + while (bungee.isRunning) { + final String line = bungee.getConsoleReader().readLine(">"); + if (line != null && !bungee.getPluginManager().dispatchCommand(ConsoleCommandSender.getInstance(), line)) { + bungee.getConsole().sendMessage(ChatColor.RED + "Command not found"); + } + } + } + + @Override + public void start() throws Exception { + this.pluginsFolder.mkdir(); + this.config.load(); + this.pluginManager.detectPlugins(this.pluginsFolder); + this.pluginManager.addInternalPlugin(new PluginEaglerSkins()); + this.pluginManager.addInternalPlugin(new PluginEaglerVoice(this.config.getVoiceEnabled())); + if (this.config.getAuthInfo().isEnabled()) { + this.authSystem = new AuthSystem(this.config.getAuthInfo()); + this.getPluginManager().registerCommand(null, new CommandChangePassword(this.authSystem)); + } + this.tokenVerify = Optional.ofNullable(System.getenv("YEEISH_TOKEN")).orElse(this.config.getTokenVerify()); + if (this.reconnectHandler == null) { + this.reconnectHandler = new SQLReconnectHandler(); + } + this.isRunning = true; + this.pluginManager.loadAndEnablePlugins(); + this.startListeners(); + this.saveThread.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + BungeeCord.this.getReconnectHandler().save(); + } + }, 0L, TimeUnit.MINUTES.toMillis(5L)); + this.reloadBanThread.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + BanList.maybeReloadBans(null); + } + }, 0L, TimeUnit.SECONDS.toMillis(3L)); + DomainBlacklist.init(this); + this.closeInactiveSockets.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + DomainBlacklist.update(); + for(WebSocketListener lst : BungeeCord.this.wsListeners) { + lst.closeInactiveSockets(); + ListenerInfo info = lst.getInfo(); + if(info.getRateLimitIP() != null) info.getRateLimitIP().deleteClearLimiters(); + if(info.getRateLimitLogin() != null) info.getRateLimitLogin().deleteClearLimiters(); + if(info.getRateLimitMOTD() != null) info.getRateLimitMOTD().deleteClearLimiters(); + if(info.getRateLimitQuery() != null) info.getRateLimitQuery().deleteClearLimiters(); + } + } + }, 0L, TimeUnit.SECONDS.toMillis(10L)); + final int authTimeout = this.config.getAuthInfo().getLoginTimeout(); + this.authTimeoutTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + AuthHandler.closeInactive(authTimeout); + } + }, 0L, TimeUnit.SECONDS.toMillis(2L)); + } + + public void startListeners() { + for (final ListenerInfo info : this.config.getListeners()) { + InetSocketAddress sock = info.getHost(); + if(info.isWebsocket()) { + sock = info.getJavaHost(); + if(sock == null) { + try { + ServerSocket s = new ServerSocket(0, 0, InetAddress.getByName("127.11.0.1")); + sock = new InetSocketAddress("127.11.0.1", s.getLocalPort()); + s.close(); + } catch(IOException e) { + sock = new InetSocketAddress("127.11.0.1",(int) (System.nanoTime() % 64000L + 1025L)); + } + } + try { + this.wsListeners.add(new WebSocketListener(info, sock, this)); + BungeeCord.this.getLogger().info("Listening websockets on " + info.getHost()); + }catch(Throwable t) { + BungeeCord.this.getLogger().log(Level.WARNING, "Could not bind websocket listener to host " + info.getHost(), t); + } + } + final InetSocketAddress sock2 = sock; + final ChannelFutureListener listener = (ChannelFutureListener) new ChannelFutureListener() { + public void operationComplete(final ChannelFuture future) throws Exception { + if (future.isSuccess()) { + BungeeCord.this.listeners.add(future.channel()); + BungeeCord.this.getLogger().info("Listening on " + sock2); + } else { + BungeeCord.this.getLogger().log(Level.WARNING, "Could not bind to host " + sock2, future.cause()); + } + } + }; + ((ServerBootstrap) ((ServerBootstrap) new ServerBootstrap().channel((Class) NioServerSocketChannel.class)).childAttr((AttributeKey) PipelineUtils.LISTENER, (Object) info).childHandler((ChannelHandler) PipelineUtils.SERVER_CHILD) + .group((EventLoopGroup) this.eventLoops).localAddress((SocketAddress) sock)).bind().addListener((GenericFutureListener) listener); + } + } + + public void stopListeners() { + for (final Channel listener : this.listeners) { + this.getLogger().log(Level.INFO, "Closing listener {0}", listener); + try { + listener.close().syncUninterruptibly(); + } catch (ChannelException ex) { + this.getLogger().severe("Could not close listen thread"); + } + } + for (final WebSocketListener listener : this.wsListeners) { + this.getLogger().log(Level.INFO, "Closing websocket listener {0}", listener.getAddress()); + try { + listener.stop(); + }catch (IOException e) { + this.getLogger().severe("Could not close listen thread"); + e.printStackTrace(); + } catch (InterruptedException e) { + this.getLogger().severe("Could not close listen thread"); + e.printStackTrace(); + } + } + this.listeners.clear(); + } + + @Override + public void stop() { + new Thread("Shutdown Thread") { + @Override + public void run() { + BungeeCord.this.isRunning = false; + BungeeCord.this.executors.shutdown(); + BungeeCord.this.stopListeners(); + BungeeCord.this.getLogger().info("Closing pending connections"); + BungeeCord.this.connectionLock.readLock().lock(); + try { + BungeeCord.this.getLogger().info("Disconnecting " + BungeeCord.this.connections.size() + " connections"); + for (final UserConnection user : BungeeCord.this.connections.values()) { + user.disconnect(BungeeCord.this.getTranslation("restart")); + } + } finally { + BungeeCord.this.connectionLock.readLock().unlock(); + } + BungeeCord.this.getLogger().info("Closing IO threads"); + BungeeCord.this.eventLoops.shutdownGracefully(); + try { + BungeeCord.this.eventLoops.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + } catch (InterruptedException ex) { + } + BungeeCord.this.getLogger().info("Saving reconnect locations"); + BungeeCord.this.reconnectHandler.save(); + BungeeCord.this.reconnectHandler.close(); + BungeeCord.this.saveThread.cancel(); + BungeeCord.this.reloadBanThread.cancel(); + BungeeCord.this.closeInactiveSockets.cancel(); + BungeeCord.this.authTimeoutTimer.cancel(); + BungeeCord.this.getLogger().info("Disabling plugins"); + for (final Plugin plugin : BungeeCord.this.pluginManager.getPlugins()) { + plugin.onDisable(); + BungeeCord.this.getScheduler().cancel(plugin); + } + BungeeCord.this.getLogger().info("Thankyou and goodbye"); + System.exit(0); + } + }.start(); + } + + public void broadcast(final DefinedPacket packet) { + this.connectionLock.readLock().lock(); + try { + for (final UserConnection con : this.connections.values()) { + con.unsafe().sendPacket(packet); + } + } finally { + this.connectionLock.readLock().unlock(); + } + } + + @Override + public String getName() { + return "BungeeCord"; + } + + @Override + public String getVersion() { + return (BungeeCord.class.getPackage().getImplementationVersion() == null) ? "unknown" : BungeeCord.class.getPackage().getImplementationVersion(); + } + + @Override + public String getTranslation(final String name, Object... args) { + String translation = ""; + try { + translation = this.bundle.getString(name); + } catch (MissingResourceException ex) { + } + return translation; + } + + @Override + public Collection getPlayers() { + this.connectionLock.readLock().lock(); + try { + return new HashSet(this.connections.values()); + } finally { + this.connectionLock.readLock().unlock(); + } + } + + @Override + public int getOnlineCount() { + return this.connections.size(); + } + + @Override + public ProxiedPlayer getPlayer(final String name) { + this.connectionLock.readLock().lock(); + try { + return this.connections.get(name); + } finally { + this.connectionLock.readLock().unlock(); + } + } + + @Override + public Map getServers() { + return (Map) this.config.getServers(); + } + + @Override + public ServerInfo getServerInfo(final String name) { + return this.getServers().get(name); + } + + @Override + public void registerChannel(final String channel) { + synchronized (this.pluginChannels) { + this.pluginChannels.add(channel); + } + } + + @Override + public void unregisterChannel(final String channel) { + synchronized (this.pluginChannels) { + this.pluginChannels.remove(channel); + } + } + + @Override + public Collection getChannels() { + synchronized (this.pluginChannels) { + return Collections.unmodifiableCollection((Collection) this.pluginChannels); + } + } + + public PacketFAPluginMessage registerChannels() { + return new PacketFAPluginMessage("REGISTER", Util.format(this.pluginChannels, "\u0000").getBytes()); + } + + @Override + public byte getProtocolVersion() { + return 78; + } + + @Override + public String getGameVersion() { + return "1.6.4"; + } + + @Override + public ServerInfo constructServerInfo(final String name, final InetSocketAddress address, final boolean restricted) { + return new BungeeServerInfo(name, address, restricted); + } + + @Override + public CommandSender getConsole() { + return ConsoleCommandSender.getInstance(); + } + + @Override + public void broadcast(final String message) { + this.getConsole().sendMessage(message); + this.broadcast(new Packet3Chat(message)); + } + + public void addConnection(final UserConnection con) { + this.connectionLock.writeLock().lock(); + try { + this.connections.put(con.getName(), con); + } finally { + this.connectionLock.writeLock().unlock(); + } + } + + public void removeConnection(final UserConnection con) { + this.connectionLock.writeLock().lock(); + try { + this.connections.remove(con.getName()); + } finally { + this.connectionLock.writeLock().unlock(); + } + } + + @Override + public CustomTabList customTabList(final ProxiedPlayer player) { + return new Custom(player); + } + + @Override + public Collection getDisabledCommands() { + return List.of(); + } + + @Override + public PluginManager getPluginManager() { + return this.pluginManager; + } + + @Override + public ReconnectHandler getReconnectHandler() { + return this.reconnectHandler; + } + + @Override + public void setReconnectHandler(final ReconnectHandler reconnectHandler) { + this.reconnectHandler = reconnectHandler; + } + + @Override + public ConfigurationAdapter getConfigurationAdapter() { + return this.configurationAdapter; + } + + @Override + public void setConfigurationAdapter(final ConfigurationAdapter configurationAdapter) { + this.configurationAdapter = configurationAdapter; + } + + @Override + public File getPluginsFolder() { + return this.pluginsFolder; + } + + @Override + public TaskScheduler getScheduler() { + return this.scheduler; + } + + public ConsoleReader getConsoleReader() { + return this.consoleReader; + } + + @Override + public Logger getLogger() { + return this.logger; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/BungeeServerInfo.java b/eaglerbungee/src/main/java/net/md_5/bungee/BungeeServerInfo.java new file mode 100644 index 0000000..82dd39d --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/BungeeServerInfo.java @@ -0,0 +1,142 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee; + +import java.beans.ConstructorProperties; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.Objects; +import java.util.Queue; + +import com.google.common.base.Preconditions; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.GenericFutureListener; +import net.md_5.bungee.api.Callback; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ServerPing; +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.connection.PingHandler; +import net.md_5.bungee.netty.HandlerBoss; +import net.md_5.bungee.netty.PipelineUtils; +import net.md_5.bungee.protocol.packet.DefinedPacket; +import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; + +public class BungeeServerInfo implements ServerInfo { + private final String name; + private final InetSocketAddress address; + private final Collection players; + private final boolean restricted; + private final Queue packetQueue; + + public void addPlayer(final ProxiedPlayer player) { + synchronized (this.players) { + this.players.add(player); + } + } + + public void removePlayer(final ProxiedPlayer player) { + synchronized (this.players) { + this.players.remove(player); + } + } + + @Override + public Collection getPlayers() { + synchronized (this.players) { + return Collections.unmodifiableCollection((Collection) this.players); + } + } + + @Override + public boolean canAccess(final CommandSender player) { + Preconditions.checkNotNull((Object) player, (Object) "player"); + return !this.restricted || player.hasPermission("bungeecord.server." + this.name); + } + + @Override + public boolean equals(final Object obj) { + return obj instanceof ServerInfo && Objects.equals(this.getAddress(), ((ServerInfo) obj).getAddress()); + } + + @Override + public int hashCode() { + return this.address.hashCode(); + } + + @Override + public void sendData(final String channel, final byte[] data) { + Preconditions.checkNotNull((Object) channel, (Object) "channel"); + Preconditions.checkNotNull((Object) data, (Object) "data"); + final Server server = this.players.isEmpty() ? null : this.players.iterator().next().getServer(); + if (server != null) { + server.sendData(channel, data); + } else { + synchronized (this.packetQueue) { + this.packetQueue.add(new PacketFAPluginMessage(channel, data)); + } + } + } + + @Override + public void ping(final Callback callback) { + Preconditions.checkNotNull((Object) callback, (Object) "callback"); + final ChannelFutureListener listener = (ChannelFutureListener) new ChannelFutureListener() { + public void operationComplete(final ChannelFuture future) throws Exception { + if (future.isSuccess()) { + ((HandlerBoss) future.channel().pipeline().get((Class) HandlerBoss.class)).setHandler(new PingHandler(BungeeServerInfo.this, callback)); + } else { + callback.done(null, future.cause()); + } + } + }; + ((Bootstrap) ((Bootstrap) ((Bootstrap) ((Bootstrap) new Bootstrap().channel((Class) NioSocketChannel.class)).group((EventLoopGroup) BungeeCord.getInstance().eventLoops)).handler((ChannelHandler) PipelineUtils.BASE)) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Integer.valueOf(5000))).remoteAddress((SocketAddress) this.getAddress()).connect().addListener((GenericFutureListener) listener); + } + + @ConstructorProperties({ "name", "address", "restricted" }) + public BungeeServerInfo(final String name, final InetSocketAddress address, final boolean restricted) { + this.players = new ArrayList(); + this.packetQueue = new LinkedList(); + this.name = name; + this.address = address; + this.restricted = restricted; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public InetSocketAddress getAddress() { + return this.address; + } + + public boolean isRestricted() { + return this.restricted; + } + + public Queue getPacketQueue() { + return this.packetQueue; + } + + @Override + public String getRedirect() { + return null; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/ConnectionThrottle.java b/eaglerbungee/src/main/java/net/md_5/bungee/ConnectionThrottle.java new file mode 100644 index 0000000..fe9660c --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/ConnectionThrottle.java @@ -0,0 +1,30 @@ +package net.md_5.bungee; + +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; + +public class ConnectionThrottle +{ + + private final Map throttle = new HashMap<>(); + private final int throttleTime; + + public ConnectionThrottle(int throttleTime) { + this.throttleTime = throttleTime; + } + + public void unthrottle(InetAddress address) + { + throttle.remove( address ); + } + + public boolean throttle(InetAddress address) + { + Long value = throttle.get( address ); + long currentTime = System.currentTimeMillis(); + + throttle.put( address, currentTime ); + return value != null && currentTime - value < throttleTime; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/EncryptionUtil.java b/eaglerbungee/src/main/java/net/md_5/bungee/EncryptionUtil.java new file mode 100644 index 0000000..c3d5ef0 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/EncryptionUtil.java @@ -0,0 +1,88 @@ +package net.md_5.bungee; + +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.Random; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import net.md_5.bungee.protocol.packet.PacketFCEncryptionResponse; +import net.md_5.bungee.protocol.packet.PacketFDEncryptionRequest; + +/** + * Class containing all encryption related methods for the proxy. + */ +public class EncryptionUtil +{ + + private static final Random random = new Random(); + public static KeyPair keys; + private static SecretKey secret = new SecretKeySpec( new byte[ 16 ], "AES" ); + + + static + { + try + { + keys = KeyPairGenerator.getInstance( "RSA" ).generateKeyPair(); + } catch ( NoSuchAlgorithmException ex ) + { + throw new ExceptionInInitializerError( ex ); + } + } + + public static PacketFDEncryptionRequest encryptRequest() + { + String hash = "-"; + byte[] pubKey = keys.getPublic().getEncoded(); + byte[] verify = new byte[ 4 ]; + random.nextBytes( verify ); + return new PacketFDEncryptionRequest( hash, pubKey, verify ); + } + + public static SecretKey getSecret() { + return EncryptionUtil.secret; + } + + public static SecretKey getSecret(PacketFCEncryptionResponse resp, PacketFDEncryptionRequest request) throws GeneralSecurityException + { + Cipher cipher = Cipher.getInstance( "RSA" ); + cipher.init( Cipher.DECRYPT_MODE, keys.getPrivate() ); + byte[] decrypted = cipher.doFinal( resp.getVerifyToken() ); + + if ( !Arrays.equals( request.getVerifyToken(), decrypted ) ) + { + throw new IllegalStateException( "Key pairs do not match!" ); + } + + cipher.init( Cipher.DECRYPT_MODE, keys.getPrivate() ); + return new SecretKeySpec( cipher.doFinal( resp.getSharedSecret() ), "AES" ); + } + + public static Cipher getCipher(int opMode, Key shared) throws GeneralSecurityException + { + Cipher cip = Cipher.getInstance( "AES/CFB8/NoPadding" ); + cip.init( opMode, shared, new IvParameterSpec( shared.getEncoded() ) ); + return cip; + } + + public static PublicKey getPubkey(PacketFDEncryptionRequest request) throws GeneralSecurityException + { + return KeyFactory.getInstance( "RSA" ).generatePublic( new X509EncodedKeySpec( request.getPublicKey() ) ); + } + + public static byte[] encrypt(Key key, byte[] b) throws GeneralSecurityException + { + Cipher hasher = Cipher.getInstance( "RSA" ); + hasher.init( Cipher.ENCRYPT_MODE, key ); + return hasher.doFinal( b ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/EntityMap.java b/eaglerbungee/src/main/java/net/md_5/bungee/EntityMap.java new file mode 100644 index 0000000..679bbaf --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/EntityMap.java @@ -0,0 +1,169 @@ +package net.md_5.bungee; + +import io.netty.buffer.ByteBuf; + +/** + * Class to rewrite integers within packets. + */ +public class EntityMap +{ + + public final static int[][] entityIds = new int[ 256 ][]; + + static + { + entityIds[0x05] = new int[] + { + 1 + }; + entityIds[0x07] = new int[] + { + 1, 5 + }; + entityIds[0x11] = new int[] + { + 1 + }; + entityIds[0x12] = new int[] + { + 1 + }; + entityIds[0x13] = new int[] + { + 1 + }; + entityIds[0x14] = new int[] + { + 1 + }; + entityIds[0x16] = new int[] + { + 1, 5 + }; + entityIds[0x17] = new int[] + { + 1 //, 20 + }; + entityIds[0x18] = new int[] + { + 1 + }; + entityIds[0x19] = new int[] + { + 1 + }; + entityIds[0x1A] = new int[] + { + 1 + }; + entityIds[0x1C] = new int[] + { + 1 + }; + entityIds[0x1E] = new int[] + { + 1 + }; + entityIds[0x1F] = new int[] + { + 1 + }; + entityIds[0x20] = new int[] + { + 1 + }; + entityIds[0x21] = new int[] + { + 1 + }; + entityIds[0x22] = new int[] + { + 1 + }; + entityIds[0x23] = new int[] + { + 1 + }; + entityIds[0x26] = new int[] + { + 1 + }; + entityIds[0x27] = new int[] + { + 1, 5 + }; + entityIds[0x28] = new int[] + { + 1 + }; + entityIds[0x29] = new int[] + { + 1 + }; + entityIds[0x2A] = new int[] + { + 1 + }; + entityIds[0x2C] = new int[] + { + 1 + }; + entityIds[0x37] = new int[] + { + 1 + }; + + entityIds[0x47] = new int[] + { + 1 + }; + } + + public static void rewrite(ByteBuf packet, int oldId, int newId) + { + int packetId = packet.getUnsignedByte( 0 ); + if ( packetId == 0x1D ) + { // bulk entity + for ( int pos = 2; pos < packet.readableBytes(); pos += 4 ) + { + int readId = packet.getInt( pos ); + if ( readId == oldId ) + { + packet.setInt( pos, newId ); + } else if ( readId == newId ) + { + packet.setInt( pos, oldId ); + } + } + } else + { + int[] idArray = entityIds[packetId]; + if ( idArray != null ) + { + for ( int pos : idArray ) + { + int readId = packet.getInt( pos ); + if ( readId == oldId ) + { + packet.setInt( pos, newId ); + } else if ( readId == newId ) + { + packet.setInt( pos, oldId ); + } + } + } + } + if ( packetId == 0x17 ) + { + int type = packet.getUnsignedByte( 5 ); + if ( type == 60 || type == 90 ) + { + int index20 = packet.getInt( 20 ); + if ( packet.readableBytes() > 24 && index20 == oldId ) + { + packet.setInt( 20, newId ); + } + } + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/PacketConstants.java b/eaglerbungee/src/main/java/net/md_5/bungee/PacketConstants.java new file mode 100644 index 0000000..e63212d --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/PacketConstants.java @@ -0,0 +1,18 @@ +package net.md_5.bungee; + +import net.md_5.bungee.protocol.packet.Packet9Respawn; +import net.md_5.bungee.protocol.packet.PacketCDClientStatus; +import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; + +public class PacketConstants +{ + + public static final Packet9Respawn DIM1_SWITCH = new Packet9Respawn( (byte) 1, (byte) 0, (byte) 0, (short) 256, "DEFAULT" ); + public static final Packet9Respawn DIM2_SWITCH = new Packet9Respawn( (byte) -1, (byte) 0, (byte) 0, (short) 256, "DEFAULT" ); + public static final PacketCDClientStatus CLIENT_LOGIN = new PacketCDClientStatus( (byte) 0 ); + public static final PacketFAPluginMessage FORGE_MOD_REQUEST = new PacketFAPluginMessage( "FML", new byte[] + { + 0, 0, 0, 0, 0, 2 + } ); + public static final PacketFAPluginMessage I_AM_BUNGEE = new PacketFAPluginMessage( "BungeeCord", new byte[ 0 ] ); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/ServerConnection.java b/eaglerbungee/src/main/java/net/md_5/bungee/ServerConnection.java new file mode 100644 index 0000000..2e0435b --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/ServerConnection.java @@ -0,0 +1,94 @@ +package net.md_5.bungee; + +import java.beans.ConstructorProperties; +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.protocol.packet.DefinedPacket; +import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; +import net.md_5.bungee.protocol.packet.PacketFFKick; + +public class ServerConnection implements Server +{ + + private final ChannelWrapper ch; + private final BungeeServerInfo info; + private boolean isObsolete; + + private Unsafe unsafe = new Unsafe() + { + @Override + public void sendPacket(DefinedPacket packet) + { + ch.write( packet ); + } + }; + + @Override + public void sendData(String channel, byte[] data) + { + unsafe().sendPacket( new PacketFAPluginMessage( channel, data ) ); + } + + @Override + public synchronized void disconnect(String reason) + { + if ( !ch.isClosed() ) + { + // TODO: Can we just use a future here? + unsafe().sendPacket( new PacketFFKick( reason ) ); + ch.getHandle().eventLoop().schedule( new Runnable() + { + @Override + public void run() + { + ch.getHandle().close(); + } + }, 100, TimeUnit.MILLISECONDS ); + } + } + + @Override + public InetSocketAddress getAddress() + { + return getInfo().getAddress(); + } + + @Override + public Unsafe unsafe() + { + return unsafe; + } + + @ConstructorProperties({ "ch", "info" }) + public ServerConnection(final ChannelWrapper ch, final BungeeServerInfo info) { + this.unsafe = new Connection.Unsafe() { + @Override + public void sendPacket(final DefinedPacket packet) { + if (ServerConnection.this.ch != null) ServerConnection.this.ch.write(packet); + } + }; + this.ch = ch; + this.info = info; + } + + public ChannelWrapper getCh() { + return this.ch; + } + + @Override + public BungeeServerInfo getInfo() { + return this.info; + } + + public boolean isObsolete() { + return this.isObsolete; + } + + public void setObsolete(final boolean isObsolete) { + this.isObsolete = isObsolete; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/ServerConnector.java b/eaglerbungee/src/main/java/net/md_5/bungee/ServerConnector.java new file mode 100644 index 0000000..8b6fdcd --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/ServerConnector.java @@ -0,0 +1,309 @@ +package net.md_5.bungee; + +import com.google.common.base.Preconditions; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import java.io.DataInput; +import java.security.PublicKey; +import java.util.Objects; +import java.util.Queue; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.event.ServerConnectedEvent; +import net.md_5.bungee.api.event.ServerKickEvent; +import net.md_5.bungee.api.event.ServerSwitchEvent; +import net.md_5.bungee.api.score.Objective; +import net.md_5.bungee.api.score.Scoreboard; +import net.md_5.bungee.api.score.Team; +import net.md_5.bungee.connection.CancelSendSignal; +import net.md_5.bungee.connection.DownstreamBridge; +import net.md_5.bungee.netty.HandlerBoss; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.netty.CipherDecoder; +import net.md_5.bungee.netty.CipherEncoder; +import net.md_5.bungee.netty.PacketDecoder; +import net.md_5.bungee.netty.PacketHandler; +import net.md_5.bungee.netty.PipelineUtils; +import net.md_5.bungee.protocol.Forge; +import net.md_5.bungee.protocol.MinecraftOutput; +import net.md_5.bungee.protocol.packet.DefinedPacket; +import net.md_5.bungee.protocol.packet.Packet1Login; +import net.md_5.bungee.protocol.packet.Packet9Respawn; +import net.md_5.bungee.protocol.packet.PacketCEScoreboardObjective; +import net.md_5.bungee.protocol.packet.PacketD1Team; +import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; +import net.md_5.bungee.protocol.packet.PacketFCEncryptionResponse; +import net.md_5.bungee.protocol.packet.PacketFDEncryptionRequest; +import net.md_5.bungee.protocol.packet.PacketFFKick; +import net.md_5.bungee.protocol.packet.forge.Forge1Login; + +public class ServerConnector extends PacketHandler +{ + private final ProxyServer bungee; + private ChannelWrapper ch; + private final UserConnection user; + private final BungeeServerInfo target; + private State thisState = State.ENCRYPT_REQUEST; + private SecretKey secretkey; + private boolean sentMessages; + + public ServerConnector(ProxyServer bungee, UserConnection user, BungeeServerInfo target){ + this.bungee = bungee; + this.user = user; + this.target = target; + } + + private enum State + { + + ENCRYPT_REQUEST, ENCRYPT_RESPONSE, LOGIN, FINISHED; + } + + @Override + public void exception(Throwable t) throws Exception + { + String message = "Exception Connecting:" + Util.exception( t ); + if ( user.getServer() == null ) + { + user.disconnect( message ); + } else + { + user.sendMessage( ChatColor.RED + message ); + } + } + + @Override + public void connected(ChannelWrapper channel) throws Exception + { + this.ch = channel; + + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF( "Login" ); + out.writeUTF( user.getAddress().getHostString() ); + out.writeInt( user.getAddress().getPort() ); + channel.write( new PacketFAPluginMessage( "BungeeCord", out.toByteArray() ) ); + + channel.write( user.getPendingConnection().getHandshake() ); + + // Skip encryption if we are not using Forge + if ( user.getPendingConnection().getForgeLogin() == null ) + { + channel.write( PacketConstants.CLIENT_LOGIN ); + } + } + + @Override + public void disconnected(ChannelWrapper channel) throws Exception + { + user.getPendingConnects().remove( target ); + } + + @Override + public void handle(Packet1Login login) throws Exception + { + Preconditions.checkState( thisState == State.LOGIN, "Not exepcting LOGIN" ); + + ServerConnection server = new ServerConnection( ch, target ); + ServerConnectedEvent event = new ServerConnectedEvent( user, server ); + bungee.getPluginManager().callEvent( event ); + + ch.write( BungeeCord.getInstance().registerChannels() ); + Queue packetQueue = target.getPacketQueue(); + synchronized ( packetQueue ) + { + while ( !packetQueue.isEmpty() ) + { + ch.write( packetQueue.poll() ); + } + } + + for ( PacketFAPluginMessage message : user.getPendingConnection().getRegisterMessages() ) + { + ch.write( message ); + } + if ( !sentMessages ) + { + for ( PacketFAPluginMessage message : user.getPendingConnection().getLoginMessages() ) + { + ch.write( message ); + } + } + + if ( user.getSettings() != null ) + { + ch.write( user.getSettings() ); + } + + synchronized ( user.getSwitchMutex() ) + { + if ( user.getServer() == null ) + { + // Once again, first connection + user.setClientEntityId( login.getEntityId() ); + user.setServerEntityId( login.getEntityId() ); + + // Set tab list size, this sucks balls, TODO: what shall we do about packet mutability + Packet1Login modLogin; + if ( ch.getHandle().pipeline().get( PacketDecoder.class ).getProtocol() == Forge.getInstance() ) + { + modLogin = new Forge1Login( login.getEntityId(), login.getLevelType(), login.getGameMode(), login.getDimension(), login.getDifficulty(), login.getUnused(), + (byte) user.getPendingConnection().getListener().getTabListSize() ); + } else + { + modLogin = new Packet1Login( login.getEntityId(), login.getLevelType(), login.getGameMode(), (byte) login.getDimension(), login.getDifficulty(), login.getUnused(), + (byte) user.getPendingConnection().getListener().getTabListSize() ); + } + user.unsafe().sendPacket( modLogin ); + + MinecraftOutput out = new MinecraftOutput(); + out.writeStringUTF8WithoutLengthHeaderBecauseDinnerboneStuffedUpTheMCBrandPacket( ProxyServer.getInstance().getName() + " (" + ProxyServer.getInstance().getVersion() + ")" ); + user.unsafe().sendPacket( new PacketFAPluginMessage( "MC|Brand", out.toArray() ) ); + } else + { + user.getTabList().onServerChange(); + + Scoreboard serverScoreboard = user.getServerSentScoreboard(); + for ( Objective objective : serverScoreboard.getObjectives() ) + { + user.unsafe().sendPacket( new PacketCEScoreboardObjective( objective.getName(), objective.getValue(), (byte) 1 ) ); + } + for ( Team team : serverScoreboard.getTeams() ) + { + user.unsafe().sendPacket( new PacketD1Team( team.getName() ) ); + } + serverScoreboard.clear(); + + user.sendDimensionSwitch(); + + user.setServerEntityId( login.getEntityId() ); + user.unsafe().sendPacket( new Packet9Respawn( login.getDimension(), login.getDifficulty(), login.getGameMode(), (short) 256, login.getLevelType() ) ); + + // Remove from old servers + user.getServer().setObsolete( true ); + user.getServer().disconnect( "Quitting" ); + } + + // TODO: Fix this? + if ( !user.isActive() ) + { + server.disconnect( "Quitting" ); + // Silly server admins see stack trace and die + bungee.getLogger().warning( "No client connected for pending server!" ); + return; + } + + // Add to new server + // TODO: Move this to the connected() method of DownstreamBridge + target.addPlayer( user ); + user.getPendingConnects().remove( target ); + + user.setServer( server ); + ch.getHandle().pipeline().get( HandlerBoss.class ).setHandler( new DownstreamBridge( bungee, user, server ) ); + } + + bungee.getPluginManager().callEvent( new ServerSwitchEvent( user ) ); + + thisState = State.FINISHED; + + throw new CancelSendSignal(); + } + + @Override + public void handle(PacketFDEncryptionRequest encryptRequest) throws Exception + { + Preconditions.checkState( thisState == State.ENCRYPT_REQUEST, "Not expecting ENCRYPT_REQUEST" ); + + // Only need to handle this if we want to use encryption + if ( user.getPendingConnection().getForgeLogin() != null ) + { + PublicKey publickey = EncryptionUtil.getPubkey( encryptRequest ); + this.secretkey = EncryptionUtil.getSecret(); + + byte[] shared = EncryptionUtil.encrypt( publickey, secretkey.getEncoded() ); + byte[] token = EncryptionUtil.encrypt( publickey, encryptRequest.getVerifyToken() ); + + ch.write( new PacketFCEncryptionResponse( shared, token ) ); + + Cipher encrypt = EncryptionUtil.getCipher( Cipher.ENCRYPT_MODE, secretkey ); + ch.addBefore( PipelineUtils.PACKET_DECODE_HANDLER, PipelineUtils.ENCRYPT_HANDLER, new CipherEncoder( encrypt ) ); + + thisState = State.ENCRYPT_RESPONSE; + } else + { + thisState = State.LOGIN; + } + } + + @Override + public void handle(PacketFCEncryptionResponse encryptResponse) throws Exception + { + Preconditions.checkState( thisState == State.ENCRYPT_RESPONSE, "Not expecting ENCRYPT_RESPONSE" ); + + Cipher decrypt = EncryptionUtil.getCipher( Cipher.DECRYPT_MODE, secretkey ); + ch.addBefore( PipelineUtils.PACKET_DECODE_HANDLER, PipelineUtils.DECRYPT_HANDLER, new CipherDecoder( decrypt ) ); + + ch.write( user.getPendingConnection().getForgeLogin() ); + + ch.write( PacketConstants.CLIENT_LOGIN ); + thisState = State.LOGIN; + } + + @Override + public void handle(PacketFFKick kick) throws Exception + { + ServerInfo def = bungee.getServerInfo( user.getPendingConnection().getListener().getFallbackServer() ); + if ( Objects.equals( target, def ) ) + { + def = null; + } + ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( user, kick.getMessage(), def, ServerKickEvent.State.CONNECTING ) ); + if ( event.isCancelled() && event.getCancelServer() != null ) + { + user.connect( event.getCancelServer() ); + return; + } + + String message = bungee.getTranslation( "connect_kick" ) + target.getName() + ": " + event.getKickReason(); + if ( user.getServer() == null ) + { + user.disconnect( message ); + } else + { + user.sendMessage( message ); + } + } + + @Override + public void handle(final PacketFAPluginMessage pluginMessage) throws Exception { + if (pluginMessage.equals(PacketConstants.I_AM_BUNGEE) && !BungeeCord.getInstance().config.allowBungeeOnBungee()) { + throw new IllegalStateException("May not connect to another BungeeCord!"); + } + if (pluginMessage.getTag().equals("FML") && (pluginMessage.getData()[0] & 0xFF) == 0x0) { + final ByteArrayDataInput in = ByteStreams.newDataInput(pluginMessage.getData()); + in.readUnsignedByte(); + for (int count = in.readInt(), i = 0; i < count; ++i) { + in.readUTF(); + } + if (in.readByte() != 0) { + ((PacketDecoder)this.ch.getHandle().pipeline().get((Class)PacketDecoder.class)).setProtocol(Forge.getInstance()); + } + } + this.user.unsafe().sendPacket(pluginMessage); + if (!this.sentMessages && this.user.getPendingConnection().getForgeLogin() != null) { + for (final PacketFAPluginMessage message : this.user.getPendingConnection().getLoginMessages()) { + this.ch.write(message); + } + this.sentMessages = true; + } + } + + @Override + public String toString() + { + return "[" + user.getName() + "] <-> ServerConnector [" + target.getName() + "]"; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/UserConnection.java b/eaglerbungee/src/main/java/net/md_5/bungee/UserConnection.java new file mode 100644 index 0000000..cb96059 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/UserConnection.java @@ -0,0 +1,447 @@ +package net.md_5.bungee; + +import com.google.common.base.Preconditions; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.internal.PlatformDependent; + +import java.beans.ConstructorProperties; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.logging.Level; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.PermissionCheckEvent; +import net.md_5.bungee.api.event.ServerConnectEvent; +import net.md_5.bungee.api.score.Scoreboard; +import net.md_5.bungee.api.tab.TabListHandler; +import net.md_5.bungee.connection.InitialHandler; +import net.md_5.bungee.eaglercraft.RedirectServerInfo; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.netty.HandlerBoss; +import net.md_5.bungee.netty.PacketWrapper; +import net.md_5.bungee.netty.PipelineUtils; +import net.md_5.bungee.protocol.packet.DefinedPacket; +import net.md_5.bungee.protocol.packet.Packet3Chat; +import net.md_5.bungee.protocol.packet.PacketCCSettings; +import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; +import net.md_5.bungee.protocol.packet.PacketFFKick; +import net.md_5.bungee.util.CaseInsensitiveSet; + +public final class UserConnection implements ProxiedPlayer +{ + + /*========================================================================*/ + private final ProxyServer bungee; + private final ChannelWrapper ch; + private final String name; + private final InitialHandler pendingConnection; + /*========================================================================*/ + private ServerConnection server; + private Object switchMutex = new Object(); + private Collection pendingConnects = new HashSet<>(); + /*========================================================================*/ + private TabListHandler tabList; + private int sentPingId; + + private long sentPingTime; + private int ping = 100; + private ServerInfo reconnectServer; + /*========================================================================*/ + private Collection groups = new CaseInsensitiveSet(); + private Collection permissions = new CaseInsensitiveSet(); + /*========================================================================*/ + + + private int serverEntityId; + private PacketCCSettings settings; + + private Scoreboard serverSentScoreboard = new Scoreboard(); + /*========================================================================*/ + + private String displayName; + /*========================================================================*/ + + private int clientEntityId; + + public void setServer(ServerConnection server) { + this.server = server; + } + + public void setSettings(PacketCCSettings settings){ + this.settings = settings; + } + + public void setServerEntityId(int serverEntityId) { + this.serverEntityId = serverEntityId; + } + + public void setSentPingId(int sentPingId) { + this.sentPingId = sentPingId; + } + + public void setSentPingTime(long sentPingTime) { + this.sentPingTime = sentPingTime; + } + + public void setPing(int ping) { + this.ping = ping; + } + + @Override + public void setReconnectServer(ServerInfo reconnectServer) { + this.reconnectServer = reconnectServer; + } + + public void setClientEntityId(int clientEntityId) { + this.clientEntityId = clientEntityId; + } + + @Override + public String getName() { + return name; + } + + @Override + public InitialHandler getPendingConnection() { + return pendingConnection; + } + + @Override + public TabListHandler getTabList() { + return tabList; + } + + public Collection getPendingConnects() { + return pendingConnects; + } + + public Object getSwitchMutex() { + return switchMutex; + } + + @Override + public ServerConnection getServer() { + return server; + } + + public int getSentPingId() { + return sentPingId; + } + + public long getSentPingTime() { + return sentPingTime; + } + + @Override + public int getPing() { + return ping; + } + + @Override + public ServerInfo getReconnectServer() { + return reconnectServer; + } + + public Collection getPermissions() { + return permissions; + } + + public int getClientEntityId() { + return clientEntityId; + } + + public int getServerEntityId() { + return serverEntityId; + } + + public PacketCCSettings getSettings() { + return settings; + } + + public Scoreboard getServerSentScoreboard() { + return serverSentScoreboard; + } + + @Override + public String getDisplayName() { + return displayName; + } + + private Unsafe unsafe = new Unsafe() + { + @Override + public void sendPacket(DefinedPacket packet) + { + ch.write( packet ); + } + }; + + @ConstructorProperties({ "bungee", "ch", "name", "pendingConnection" }) + public UserConnection(final ProxyServer bungee, final ChannelWrapper ch, final String name, final InitialHandler pendingConnection) { + this.switchMutex = new Object(); + this.pendingConnects = new HashSet(); + this.ping = 100; + this.groups = (Collection) new CaseInsensitiveSet(); + this.permissions = (Collection) new CaseInsensitiveSet(); + this.serverSentScoreboard = new Scoreboard(); + this.unsafe = new Connection.Unsafe() { + @Override + public void sendPacket(final DefinedPacket packet) { + UserConnection.this.ch.write(packet); + } + }; + if (bungee == null) { + throw new NullPointerException("bungee"); + } + if (ch == null) { + throw new NullPointerException("ch"); + } + if (name == null) { + throw new NullPointerException("name"); + } + this.bungee = bungee; + this.ch = ch; + this.name = name; + this.pendingConnection = pendingConnection; + } + + public void init() + { + this.displayName = name; + try + { + this.tabList = getPendingConnection().getListener().getTabList().getDeclaredConstructor().newInstance(); + } catch ( ReflectiveOperationException ex ) + { + throw new RuntimeException( ex ); + } + this.tabList.init( this ); + + Collection g = bungee.getConfigurationAdapter().getGroups( name ); + for ( String s : g ) + { + addGroups( s ); + } + } + + @Override + public void setTabList(TabListHandler tabList) + { + tabList.init( this ); + this.tabList = tabList; + } + + public void sendPacket(PacketWrapper packet) + { + ch.write( packet ); + } + + @Deprecated + public boolean isActive() + { + return !ch.isClosed(); + } + + @Override + public void setDisplayName(String name) + { + Preconditions.checkNotNull( name, "displayName" ); + Preconditions.checkArgument( name.length() <= 16, "Display name cannot be longer than 16 characters" ); + getTabList().onDisconnect(); + displayName = name; + getTabList().onConnect(); + } + + @Override + public void connect(ServerInfo target) + { + connect( target, false ); + } + + void sendDimensionSwitch() + { + unsafe().sendPacket( PacketConstants.DIM1_SWITCH ); + unsafe().sendPacket( PacketConstants.DIM2_SWITCH ); + } + + public void connectNow(ServerInfo target) + { + sendDimensionSwitch(); + connect( target ); + } + + public void connect(final ServerInfo info, final boolean retry) { + if(info instanceof RedirectServerInfo) { + sendData("EAG|Reconnect", ((RedirectServerInfo)info).getRedirect().getBytes(StandardCharsets.UTF_8)); + return; + } + final ServerConnectEvent event = new ServerConnectEvent(this, info); + if (this.bungee.getPluginManager().callEvent(event).isCancelled()) { + return; + } + final BungeeServerInfo target = (BungeeServerInfo) event.getTarget(); + if (this.getServer() != null && this.getServer().getInfo() != null && Objects.equals(this.getServer().getInfo(), target)) { + //this.sendMessage(ChatColor.RED + "Cannot connect to server you are already on!"); + return; + } + if (this.pendingConnects.contains(target)) { + this.sendMessage(ChatColor.RED + "Already connecting to this server!"); + return; + } + this.pendingConnects.add(target); + final ChannelInitializer initializer = new ChannelInitializer() { + protected void initChannel(final Channel ch) throws Exception { + PipelineUtils.BASE.initChannel(ch); + ((HandlerBoss) ch.pipeline().get((Class) HandlerBoss.class)).setHandler(new ServerConnector(UserConnection.this.bungee, UserConnection.this, target)); + } + }; + final ChannelFutureListener listener = (ChannelFutureListener) new ChannelFutureListener() { + public void operationComplete(final ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + future.channel().close(); + UserConnection.this.pendingConnects.remove(target); + final ServerInfo def = ProxyServer.getInstance().getServers().get(UserConnection.this.getPendingConnection().getListener().getFallbackServer()); + if ((retry & target != def) && ((UserConnection.this.getServer() == null || UserConnection.this.getServer().getInfo() == null) || def != UserConnection.this.getServer().getInfo())) { + UserConnection.this.sendMessage(UserConnection.this.bungee.getTranslation("fallback_lobby")); + UserConnection.this.connect(def, false); + } else if (UserConnection.this.getServer() == null || UserConnection.this.getServer().getInfo() == null) { + UserConnection.this.disconnect(UserConnection.this.bungee.getTranslation("fallback_kick") + future.cause().getClass().getName()); + } else { + UserConnection.this.sendMessage(UserConnection.this.bungee.getTranslation("fallback_kick") + future.cause().getClass().getName()); + } + } + } + }; + final Bootstrap b = ((Bootstrap) ((Bootstrap) ((Bootstrap) ((Bootstrap) new Bootstrap().channel((Class) NioSocketChannel.class)).group((EventLoopGroup) BungeeCord.getInstance().eventLoops)).handler((ChannelHandler) initializer)) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Integer.valueOf(5000))).remoteAddress((SocketAddress) target.getAddress()); + if (!PlatformDependent.isWindows()) { + b.localAddress(this.getPendingConnection().getListener().getHost().getHostString(), 0); + } + b.connect().addListener((GenericFutureListener) listener); + } + + @Override + public synchronized void disconnect(String reason) + { + if ( ch.getHandle().isActive() ) + { + bungee.getLogger().log( Level.INFO, "[" + getName() + "] disconnected with: " + reason ); + unsafe().sendPacket( new PacketFFKick( reason ) ); + ch.close(); + if ( server != null ) + { + server.disconnect( "Quitting" ); + } + } + } + + @Override + public void chat(String message) + { + Preconditions.checkState( server != null, "Not connected to server" ); + server.getCh().write( new Packet3Chat( message ) ); + } + + @Override + public void sendMessage(String message) + { + this.unsafe().sendPacket(new Packet3Chat(message)); + } + + @Override + public void sendMessages(String... messages) + { + for ( String message : messages ) + { + sendMessage( message ); + } + } + + @Override + public void sendData(String channel, byte[] data) + { + unsafe().sendPacket( new PacketFAPluginMessage( channel, data ) ); + } + + @Override + public InetSocketAddress getAddress() + { + return (InetSocketAddress) ch.getHandle().remoteAddress(); + } + + @Override + public Collection getGroups() + { + return Collections.unmodifiableCollection( groups ); + } + + @Override + public void addGroups(String... groups) + { + for ( String group : groups ) + { + this.groups.add( group ); + for ( String permission : bungee.getConfigurationAdapter().getPermissions( group ) ) + { + setPermission( permission, true ); + } + } + } + + @Override + public void removeGroups(String... groups) + { + for ( String group : groups ) + { + this.groups.remove( group ); + for ( String permission : bungee.getConfigurationAdapter().getPermissions( group ) ) + { + setPermission( permission, false ); + } + } + } + + @Override + public boolean hasPermission(String permission) + { + return bungee.getPluginManager().callEvent( new PermissionCheckEvent( this, permission, permissions.contains( permission ) ) ).hasPermission(); + } + + @Override + public void setPermission(String permission, boolean value) + { + if ( value ) + { + permissions.add( permission ); + } else + { + permissions.remove( permission ); + } + } + + @Override + public Map getAttachment() { // fix this (maybe) + System.out.println("This might be a problem"); + return Map.of(); + } + + @Override + public String toString() + { + return name; + } + + @Override + public Unsafe unsafe() + { + return unsafe; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/Util.java b/eaglerbungee/src/main/java/net/md_5/bungee/Util.java new file mode 100644 index 0000000..b42808f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/Util.java @@ -0,0 +1,73 @@ +package net.md_5.bungee; + +import java.net.InetSocketAddress; +import java.util.Collection; + +/** + * Series of utility classes to perform various operations. + */ +public class Util +{ + + private static final int DEFAULT_PORT = 25565; + + /** + * Method to transform human readable addresses into usable address objects. + * + * @param hostline in the format of 'host:port' + * @return the constructed hostname + port. + */ + public static InetSocketAddress getAddr(String hostline) + { + String[] split = hostline.split( ":" ); + int port = DEFAULT_PORT; + if ( split.length > 1 ) + { + port = Integer.parseInt( split[1] ); + } + return new InetSocketAddress( split[0], port ); + } + + /** + * Formats an integer as a hex value. + * + * @param i the integer to format + * @return the hex representation of the integer + */ + public static String hex(int i) + { + return String.format( "0x%02X", i ); + } + + /** + * Constructs a pretty one line version of a {@link Throwable}. Useful for + * debugging. + * + * @param t the {@link Throwable} to format. + * @return a string representing information about the {@link Throwable} + */ + public static String exception(Throwable t) + { + // TODO: We should use clear manually written exceptions + StackTraceElement[] trace = t.getStackTrace(); + return t.getClass().getSimpleName() + " : " + t.getMessage() + + ( ( trace.length > 0 ) ? " @ " + t.getStackTrace()[0].getClassName() + ":" + t.getStackTrace()[0].getLineNumber() : "" ); + } + + public static String csv(Iterable objects) + { + return format( objects, ", " ); + } + + public static String format(Iterable objects, String separators) + { + StringBuilder ret = new StringBuilder(); + for ( Object o : objects ) + { + ret.append( o ); + ret.append( separators ); + } + + return ( ret.length() == 0 ) ? "" : ret.substring( 0, ret.length() - separators.length() ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/AbstractReconnectHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/AbstractReconnectHandler.java new file mode 100644 index 0000000..2adcd57 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/AbstractReconnectHandler.java @@ -0,0 +1,46 @@ +package net.md_5.bungee.api; + +import com.google.common.base.Preconditions; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public abstract class AbstractReconnectHandler implements ReconnectHandler +{ + + @Override + public ServerInfo getServer(ProxiedPlayer player) + { + ServerInfo server = getForcedHost( player.getPendingConnection() ); + if ( server == null ) + { + server = getStoredServer( player ); + if ( server == null ) + { + server = ProxyServer.getInstance().getServerInfo( player.getPendingConnection().getListener().getDefaultServer() ); + } + + Preconditions.checkState( server != null, "Default server not defined" ); + } + + return server; + } + + public static ServerInfo getForcedHost(PendingConnection con) + { + if ( con.getVirtualHost() == null ) + { + return null; + } + + String forced = con.getListener().getForcedHosts().get( con.getVirtualHost().getHostString() ); + + if ( forced == null && con.getListener().isForceDefault() ) + { + forced = con.getListener().getDefaultServer(); + } + return ProxyServer.getInstance().getServerInfo( forced ); + } + + protected abstract ServerInfo getStoredServer(ProxiedPlayer player); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/Callback.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/Callback.java new file mode 100644 index 0000000..0cccc17 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/Callback.java @@ -0,0 +1,19 @@ +package net.md_5.bungee.api; + +/** + * Represents a method which may be called once a result has been computed + * asynchronously. + * + * @param the type of result + */ +public interface Callback +{ + + /** + * Called when the result is done. + * + * @param result the result of the computation + * @param error the error(s) that occurred, if any + */ + public void done(V result, Throwable error); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/ChatColor.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/ChatColor.java new file mode 100644 index 0000000..0df1cd5 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/ChatColor.java @@ -0,0 +1,186 @@ +package net.md_5.bungee.api; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Simplistic enumeration of all supported color values for chat. + */ +public enum ChatColor +{ + + /** + * Represents black. + */ + BLACK( '0' ), + /** + * Represents dark blue. + */ + DARK_BLUE( '1' ), + /** + * Represents dark green. + */ + DARK_GREEN( '2' ), + /** + * Represents dark blue (aqua). + */ + DARK_AQUA( '3' ), + /** + * Represents dark red. + */ + DARK_RED( '4' ), + /** + * Represents dark purple. + */ + DARK_PURPLE( '5' ), + /** + * Represents gold. + */ + GOLD( '6' ), + /** + * Represents gray. + */ + GRAY( '7' ), + /** + * Represents dark gray. + */ + DARK_GRAY( '8' ), + /** + * Represents blue. + */ + BLUE( '9' ), + /** + * Represents green. + */ + GREEN( 'a' ), + /** + * Represents aqua. + */ + AQUA( 'b' ), + /** + * Represents red. + */ + RED( 'c' ), + /** + * Represents light purple. + */ + LIGHT_PURPLE( 'd' ), + /** + * Represents yellow. + */ + YELLOW( 'e' ), + /** + * Represents white. + */ + WHITE( 'f' ), + /** + * Represents magical characters that change around randomly. + */ + MAGIC( 'k' ), + /** + * Makes the text bold. + */ + BOLD( 'l' ), + /** + * Makes a line appear through the text. + */ + STRIKETHROUGH( 'm' ), + /** + * Makes the text appear underlined. + */ + UNDERLINE( 'n' ), + /** + * Makes the text italic. + */ + ITALIC( 'o' ), + /** + * Resets all previous chat colors or formats. + */ + RESET( 'r' ); + /** + * The special character which prefixes all chat colour codes. Use this if + * you need to dynamically convert colour codes from your custom format. + */ + public static final char COLOR_CHAR = '\u00A7'; + /** + * Pattern to remove all colour codes. + */ + private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile( "(?i)" + String.valueOf( COLOR_CHAR ) + "[0-9A-FK-OR]" ); + /** + * Colour instances keyed by their active character. + */ + private static final Map BY_CHAR = new HashMap<>(); + /** + * The code appended to {@link #COLOR_CHAR} to make usable colour. + */ + private final char code; + /** + * This colour's colour char prefixed by the {@link #COLOR_CHAR}. + */ + private final String toString; + + static + { + for ( ChatColor colour : values() ) + { + BY_CHAR.put( colour.code, colour ); + } + } + + private ChatColor(char code) + { + this.code = code; + this.toString = new String( new char[] + { + COLOR_CHAR, code + } ); + } + + @Override + public String toString() + { + return toString; + } + + /** + * Strips the given message of all color codes + * + * @param input String to strip of color + * @return A copy of the input string, without any coloring + */ + public static String stripColor(final String input) + { + if ( input == null ) + { + return null; + } + + return STRIP_COLOR_PATTERN.matcher( input ).replaceAll( "" ); + } + + public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) + { + char[] b = textToTranslate.toCharArray(); + for ( int i = 0; i < b.length - 1; i++ ) + { + if ( b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf( b[i + 1] ) > -1 ) + { + b[i] = ChatColor.COLOR_CHAR; + b[i + 1] = Character.toLowerCase( b[i + 1] ); + } + } + return new String( b ); + } + + /** + * Get the colour represented by the specified code. + * + * @param code the code to search for + * @return the mapped colour, or null if non exists + */ + public static ChatColor getByChar(char code) + { + return BY_CHAR.get( code ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/CommandSender.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/CommandSender.java new file mode 100644 index 0000000..5bd40e3 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/CommandSender.java @@ -0,0 +1,70 @@ +package net.md_5.bungee.api; + +import java.util.Collection; +import java.util.Map; + +public interface CommandSender +{ + + /** + * Get the unique name of this command sender. + * + * @return the senders username + */ + public String getName(); + + /** + * Send a message to this sender. + * + * @param message the message to send + */ + public void sendMessage(String message); + + /** + * Send several messages to this sender. Each message will be sent + * separately. + * + * @param messages the messages to send + */ + public void sendMessages(String... messages); + + /** + * Get all groups this user is part of. This returns an unmodifiable + * collection. + * + * @return the users groups + */ + public Collection getGroups(); + + /** + * Adds groups to a this user for the current session only. + * + * @param groups the groups to add + */ + public void addGroups(String... groups); + + /** + * Remove groups from this user for the current session only. + * + * @param groups the groups to remove + */ + public void removeGroups(String... groups); + + /** + * Checks if this user has the specified permission node. + * + * @param permission the node to check + * @return whether they have this node + */ + public boolean hasPermission(String permission); + + /** + * Set a permission node for this user. + * + * @param permission the node to set + * @param value the value of the node + */ + public void setPermission(String permission, boolean value); + + Map getAttachment(); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/MOTD.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/MOTD.java new file mode 100644 index 0000000..8608aa6 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/MOTD.java @@ -0,0 +1,25 @@ +package net.md_5.bungee.api; + +import java.util.List; + +public interface MOTD extends QueryConnection { + + public void sendToUser(); + + public String getLine1(); + public String getLine2(); + public List getPlayerList(); + public int[] getBitmap(); + public int getOnlinePlayers(); + public int getMaxPlayers(); + public String getSubType(); + + public void setLine1(String p); + public void setLine2(String p); + public void setPlayerList(List p); + public void setPlayerList(String... p); + public void setBitmap(int[] p); + public void setOnlinePlayers(int i); + public void setMaxPlayers(int i); + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/ProxyServer.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/ProxyServer.java new file mode 100644 index 0000000..a91ead3 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/ProxyServer.java @@ -0,0 +1,251 @@ +package net.md_5.bungee.api; + +import net.md_5.bungee.api.plugin.PluginManager; +import com.google.common.base.Preconditions; +import java.io.File; +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.Map; +import java.util.logging.Logger; +import net.md_5.bungee.api.config.ConfigurationAdapter; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.scheduler.TaskScheduler; +import net.md_5.bungee.api.tab.CustomTabList; + +public abstract class ProxyServer +{ + + private static ProxyServer instance; + + public static ProxyServer getInstance(){ + return instance; + } + + /** + * Sets the proxy instance. This method may only be called once per an + * application. + * + * @param instance the new instance to set + */ + public static void setInstance(ProxyServer instance) + { + Preconditions.checkNotNull( instance, "instance" ); + Preconditions.checkArgument( ProxyServer.instance == null, "Instance already set" ); + ProxyServer.instance = instance; + } + + /** + * Gets the name of the currently running proxy software. + * + * @return the name of this instance + */ + public abstract String getName(); + + /** + * Gets the version of the currently running proxy software. + * + * @return the version of this instance + */ + public abstract String getVersion(); + + /** + * Gets a localized string from the .properties file. + * + * @return the localized string + */ + public abstract String getTranslation(String name, Object... args); + + /** + * Gets the main logger which can be used as a suitable replacement for + * {@link System#out} and {@link System#err}. + * + * @return the {@link Logger} instance + */ + public abstract Logger getLogger(); + + /** + * Return all players currently connected. + * + * @return all connected players + */ + public abstract Collection getPlayers(); + + /** + * Gets a connected player via their unique username. + * + * @param name of the player + * @return their player instance + */ + public abstract ProxiedPlayer getPlayer(String name); + + /** + * Return all servers registered to this proxy, keyed by name. Unlike the + * methods in {@link ConfigurationAdapter#getServers()}, this will not + * return a fresh map each time. + * + * @return all registered remote server destinations + */ + public abstract Map getServers(); + + /** + * Gets the server info of a server. + * + * @param name the name of the configured server + * @return the server info belonging to the specified server + */ + public abstract ServerInfo getServerInfo(String name); + + /** + * Get the {@link PluginManager} associated with loading plugins and + * dispatching events. It is recommended that implementations use the + * provided PluginManager class. + * + * @return the plugin manager + */ + public abstract PluginManager getPluginManager(); + + /** + * Returns the currently in use configuration adapter. + * + * @return the used configuration adapter + */ + public abstract ConfigurationAdapter getConfigurationAdapter(); + + /** + * Set the configuration adapter to be used. Must be called from + * {@link Plugin#onLoad()}. + * + * @param adapter the adapter to use + */ + public abstract void setConfigurationAdapter(ConfigurationAdapter adapter); + + /** + * Get the currently in use reconnect handler. + * + * @return the in use reconnect handler + */ + public abstract ReconnectHandler getReconnectHandler(); + + /** + * Sets the reconnect handler to be used for subsequent connections. + * + * @param handler the new handler + */ + public abstract void setReconnectHandler(ReconnectHandler handler); + + /** + * Gracefully mark this instance for shutdown. + */ + public abstract void stop(); + + /** + * Start this instance so that it may accept connections. + * + * @throws Exception any exception thrown during startup causing the + * instance to fail to boot + */ + public abstract void start() throws Exception; + + /** + * Register a channel for use with plugin messages. This is required by some + * server / client implementations. + * + * @param channel the channel to register + */ + public abstract void registerChannel(String channel); + + /** + * Unregister a previously registered channel. + * + * @param channel the channel to unregister + */ + public abstract void unregisterChannel(String channel); + + /** + * Get an immutable set of all registered plugin channels. + * + * @return registered plugin channels + */ + public abstract Collection getChannels(); + + /** + * Get the Minecraft version supported by this proxy. + * + * @return the supported Minecraft version + */ + public abstract String getGameVersion(); + + /** + * Get the Minecraft protocol version supported by this proxy. + * + * @return the Minecraft protocol version + */ + public abstract byte getProtocolVersion(); + + /** + * Factory method to construct an implementation specific server info + * instance. + * + * @param name name of the server + * @param address connectable Minecraft address + port of the server + * @param restricted whether the server info restricted property will be set + * @return the constructed instance + */ + public abstract ServerInfo constructServerInfo(String name, InetSocketAddress address, boolean restricted); + + /** + * Returns the console overlord for this proxy. Being the console, this + * command server cannot have permissions or groups, and will be able to + * execute anything. + * + * @return the console command sender of this proxy + */ + public abstract CommandSender getConsole(); + + /** + * Return the folder used to load plugins from. + * + * @return the folder used to load plugin + */ + public abstract File getPluginsFolder(); + + /** + * Get the scheduler instance for this proxy. + * + * @return the in use scheduler + */ + public abstract TaskScheduler getScheduler(); + + /** + * Get the current number of connected users. The default implementation is + * more efficient than {@link #getPlayers()} as it does not take a lock or + * make a copy. + * + * @return the current number of connected players + */ + public abstract int getOnlineCount(); + + /** + * Send the specified message to the console and all connected players. + * + * @param message the message to broadcast + */ + public abstract void broadcast(String message); + + /** + * Gets a new instance of this proxies custom tab list. + * + * @param player the player to generate this list in the context of + * @return a new {@link CustomTabList} instance + */ + public abstract CustomTabList customTabList(ProxiedPlayer player); + + /** + * Gets the commands which are disabled and will not be run on this proxy. + * + * @return the set of disabled commands + */ + public abstract Collection getDisabledCommands(); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/QueryConnection.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/QueryConnection.java new file mode 100644 index 0000000..dbca191 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/QueryConnection.java @@ -0,0 +1,70 @@ +package net.md_5.bungee.api; + +import java.net.InetAddress; + +import org.json.JSONObject; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.eaglercraft.EaglercraftBungee; + +public interface QueryConnection { + + public InetAddress getRemoteAddress(); + public ListenerInfo getListener(); + + public String getAccept(); + public void setReturnType(String type); + public String getReturnType(); + + public int availableRequests(); + + public default JSONObject readRequestData() { + String s = readRequestString(); + return s == null ? null : new JSONObject(s); + } + + public String readRequestString(); + public long getConnectionTimestamp(); + + public default long getConnectionAge() { + return System.currentTimeMillis() - getConnectionTimestamp(); + } + + public default void writeResponse(JSONObject msg) { + JSONObject toSend = new JSONObject(); + toSend.put("type", getReturnType()); + toSend.put("name", BungeeCord.getInstance().config.getServerName()); + toSend.put("brand", EaglercraftBungee.brand); + toSend.put("vers", EaglercraftBungee.version); + toSend.put("cracked", EaglercraftBungee.cracked); + toSend.put("secure", false); + toSend.put("time", System.currentTimeMillis()); + toSend.put("uuid", BungeeCord.getInstance().config.getUuid()); + toSend.put("data", msg); + writeResponseRaw(toSend.toString()); + } + + public default void writeResponse(String msg) { + JSONObject toSend = new JSONObject(); + toSend.put("type", getReturnType()); + toSend.put("name", BungeeCord.getInstance().config.getServerName()); + toSend.put("brand", EaglercraftBungee.brand); + toSend.put("vers", EaglercraftBungee.version); + toSend.put("cracked", EaglercraftBungee.cracked); + toSend.put("secure", false); + toSend.put("time", System.currentTimeMillis()); + toSend.put("uuid", BungeeCord.getInstance().config.getUuid()); + toSend.put("data", msg); + writeResponseRaw(toSend.toString()); + } + + public void writeResponseRaw(String msg); + public void writeResponseBinary(byte[] blob); + + public void keepAlive(boolean yes); + public boolean shouldKeepAlive(); + public boolean isClosed(); + public void close(); + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/ReconnectHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/ReconnectHandler.java new file mode 100644 index 0000000..8b1b48a --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/ReconnectHandler.java @@ -0,0 +1,39 @@ +package net.md_5.bungee.api; + +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public interface ReconnectHandler +{ + + /** + * Gets the initial server name for a connecting player. + * + * @param player the connecting player + * @return the server to connect to + */ + ServerInfo getServer(ProxiedPlayer player); + + /** + * Save the server of this player before they disconnect so it can be + * retrieved later. + * + * @param player the player to save + */ + void setServer(ProxiedPlayer player); // TOOD: String + String arguments? + + /** + * Save all pending reconnect locations. Whilst not used for database + * connections, this method will be called at a predefined interval to allow + * the saving of reconnect files. + */ + void save(); + + /** + * Close all connections indicating that the proxy is about to shutdown and + * all data should be saved. No new requests will be made after this method + * has been called. + * + */ + void close(); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/ServerIcon.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/ServerIcon.java new file mode 100644 index 0000000..ea4d8b1 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/ServerIcon.java @@ -0,0 +1,66 @@ +package net.md_5.bungee.api; + +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; + +public class ServerIcon { + + public 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; + } + + public static int[] createServerIcon(InputStream f) { + try { + return createServerIcon(ImageIO.read(f)); + }catch(Throwable t) { + return null; + } + } + + public static int[] createServerIcon(File f) { + try { + return createServerIcon(ImageIO.read(f)); + }catch(Throwable t) { + return null; + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/ServerPing.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/ServerPing.java new file mode 100644 index 0000000..de83dac --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/ServerPing.java @@ -0,0 +1,107 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api; + +import java.beans.ConstructorProperties; + +public class ServerPing { + private final byte protocolVersion; + private final String gameVersion; + private final String motd; + private final int currentPlayers; + private final int maxPlayers; + + @ConstructorProperties({ "protocolVersion", "gameVersion", "motd", "currentPlayers", "maxPlayers" }) + public ServerPing(final byte protocolVersion, final String gameVersion, final String motd, final int currentPlayers, final int maxPlayers) { + this.protocolVersion = protocolVersion; + this.gameVersion = gameVersion; + this.motd = motd; + this.currentPlayers = currentPlayers; + this.maxPlayers = maxPlayers; + } + + public byte getProtocolVersion() { + return this.protocolVersion; + } + + public String getGameVersion() { + return this.gameVersion; + } + + public String getMotd() { + return this.motd; + } + + public int getCurrentPlayers() { + return this.currentPlayers; + } + + public int getMaxPlayers() { + return this.maxPlayers; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof ServerPing)) { + return false; + } + final ServerPing other = (ServerPing) o; + if (!other.canEqual(this)) { + return false; + } + if (this.getProtocolVersion() != other.getProtocolVersion()) { + return false; + } + final Object this$gameVersion = this.getGameVersion(); + final Object other$gameVersion = other.getGameVersion(); + Label_0078: { + if (this$gameVersion == null) { + if (other$gameVersion == null) { + break Label_0078; + } + } else if (this$gameVersion.equals(other$gameVersion)) { + break Label_0078; + } + return false; + } + final Object this$motd = this.getMotd(); + final Object other$motd = other.getMotd(); + if (this$motd == null) { + if (other$motd == null) { + return this.getCurrentPlayers() == other.getCurrentPlayers() && this.getMaxPlayers() == other.getMaxPlayers(); + } + } else if (this$motd.equals(other$motd)) { + return this.getCurrentPlayers() == other.getCurrentPlayers() && this.getMaxPlayers() == other.getMaxPlayers(); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof ServerPing; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + this.getProtocolVersion(); + final Object $gameVersion = this.getGameVersion(); + result = result * 31 + (($gameVersion == null) ? 0 : $gameVersion.hashCode()); + final Object $motd = this.getMotd(); + result = result * 31 + (($motd == null) ? 0 : $motd.hashCode()); + result = result * 31 + this.getCurrentPlayers(); + result = result * 31 + this.getMaxPlayers(); + return result; + } + + @Override + public String toString() { + return "ServerPing(protocolVersion=" + this.getProtocolVersion() + ", gameVersion=" + this.getGameVersion() + ", motd=" + this.getMotd() + ", currentPlayers=" + this.getCurrentPlayers() + ", maxPlayers=" + this.getMaxPlayers() + + ")"; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/config/AuthServiceInfo.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/AuthServiceInfo.java new file mode 100644 index 0000000..000d4dd --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/AuthServiceInfo.java @@ -0,0 +1,48 @@ +package net.md_5.bungee.api.config; + +import java.util.List; + +public class AuthServiceInfo { + + private final boolean enabled; + private final boolean registerEnabled; + private final String authfile; + private final int ipLimit; + private final List joinMessages; + private final int loginTimeout; + + public AuthServiceInfo(boolean enabled, boolean registerEnabled, String authfile, + int timeout, List joinMessages, int loginTimeout) { + this.enabled = enabled; + this.registerEnabled = registerEnabled; + this.authfile = authfile; + this.ipLimit = timeout; + this.joinMessages = joinMessages; + this.loginTimeout = loginTimeout; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isRegisterEnabled() { + return registerEnabled; + } + + public String getAuthfile() { + return authfile; + } + + public int getIpLimit() { + return ipLimit; + } + + public List getJoinMessages() { + return joinMessages; + } + + public int getLoginTimeout() { + return loginTimeout; + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/config/ConfigurationAdapter.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/ConfigurationAdapter.java new file mode 100644 index 0000000..03dac47 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/ConfigurationAdapter.java @@ -0,0 +1,40 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.config; + +import java.util.Collection; +import java.util.Map; + +public interface ConfigurationAdapter { + void load(); + + int getInt(final String p0, final int p1); + + String getString(final String p0, final String p1); + + boolean getBoolean(final String p0, final boolean p1); + + Map getServers(); + + Collection getListeners(); + + Collection getGroups(final String p0); + + Collection getPermissions(final String p0); + + Collection getBlacklistURLs(); + + Collection getBlacklistSimpleWhitelist(); + + Collection getDisabledCommands(); + + Collection getICEServers(); + + AuthServiceInfo getAuthSettings(); + + Map getMap(); + + void forceSave(); +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/config/ListenerInfo.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/ListenerInfo.java new file mode 100644 index 0000000..bc9e244 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/ListenerInfo.java @@ -0,0 +1,360 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.config; + +import java.io.File; +import java.net.InetSocketAddress; +import java.util.Map; + +import net.md_5.bungee.api.ServerIcon; +import net.md_5.bungee.api.tab.TabListHandler; +import net.md_5.bungee.eaglercraft.WebSocketRateLimiter; + +public class ListenerInfo { + private final String hostString; + private final InetSocketAddress host; + private final InetSocketAddress javaHost; + private final String motd; + private final int maxPlayers; + private final int tabListSize; + private final String defaultServer; + private final String fallbackServer; + private final boolean forceDefault; + private final boolean websocket; + private final boolean forwardIp; + private final String forwardIpHeader; + private final Map forcedHosts; + private final TexturePackInfo texturePack; + private final Class tabList; + private final String serverIcon; + private final int[] serverIconCache; + private boolean serverIconLoaded; + private boolean serverIconSet; + private final boolean allowMOTD; + private final boolean allowQuery; + private final MOTDCacheConfiguration cacheConfig; + private final WebSocketRateLimiter rateLimitIP; + private final WebSocketRateLimiter rateLimitLogin; + private final WebSocketRateLimiter rateLimitMOTD; + private final WebSocketRateLimiter rateLimitQuery; + + + public ListenerInfo(final String hostString, final InetSocketAddress host, final InetSocketAddress javaHost, + final String forwardIpHeader, final String motd, final int maxPlayers, final int tabListSize, + final String defaultServer, final String fallbackServer, final boolean forceDefault, + final boolean websocket, final boolean forwardIp, final Map forcedHosts, + final TexturePackInfo texturePack, final Class tabList, final String serverIcon, + final MOTDCacheConfiguration cacheConfig, final boolean allowMOTD, final boolean allowQuery, + final WebSocketRateLimiter rateLimitIP, final WebSocketRateLimiter rateLimitLogin, + final WebSocketRateLimiter rateLimitMOTD, final WebSocketRateLimiter rateLimitQuery) { + this.hostString = hostString; + this.host = host; + this.javaHost = javaHost; + this.motd = motd; + this.maxPlayers = maxPlayers; + this.tabListSize = tabListSize; + this.defaultServer = defaultServer; + this.fallbackServer = fallbackServer; + this.forceDefault = forceDefault; + this.websocket = websocket; + this.forwardIp = forwardIp; + this.forwardIpHeader = forwardIpHeader; + this.forcedHosts = forcedHosts; + this.texturePack = texturePack; + this.tabList = tabList; + this.serverIcon = serverIcon; + this.serverIconCache = new int[4096]; + this.serverIconLoaded = false; + this.serverIconSet = false; + this.allowMOTD = allowMOTD; + this.allowQuery = allowQuery; + this.cacheConfig = cacheConfig; + this.rateLimitIP = rateLimitIP; + this.rateLimitLogin = rateLimitLogin; + this.rateLimitMOTD = rateLimitMOTD; + this.rateLimitQuery = rateLimitQuery; + } + + public String getHostString() { + return this.hostString; + } + + public InetSocketAddress getHost() { + return this.host; + } + + public InetSocketAddress getJavaHost() { + return this.javaHost; + } + + public String getMotd() { + return this.motd; + } + + public int getMaxPlayers() { + return this.maxPlayers; + } + + public int getTabListSize() { + return this.tabListSize; + } + + public String getDefaultServer() { + return this.defaultServer; + } + + public String getFallbackServer() { + return this.fallbackServer; + } + + public boolean isForceDefault() { + return this.forceDefault; + } + + public Map getForcedHosts() { + return this.forcedHosts; + } + + public TexturePackInfo getTexturePack() { + return this.texturePack; + } + + public Class getTabList() { + return this.tabList; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof ListenerInfo)) { + return false; + } + final ListenerInfo other = (ListenerInfo) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$host = this.getHost(); + final Object other$host = other.getHost(); + Label_0065: { + if (this$host == null) { + if (other$host == null) { + break Label_0065; + } + } else if (this$host.equals(other$host)) { + break Label_0065; + } + return false; + } + final Object this$motd = this.getMotd(); + final Object other$motd = other.getMotd(); + Label_0102: { + if (this$motd == null) { + if (other$motd == null) { + break Label_0102; + } + } else if (this$motd.equals(other$motd)) { + break Label_0102; + } + return false; + } + if (this.getMaxPlayers() != other.getMaxPlayers()) { + return false; + } + if (this.getTabListSize() != other.getTabListSize()) { + return false; + } + if (this.isWebsocket() != other.isWebsocket()) { + return false; + } + final Object this$defaultServer = this.getDefaultServer(); + final Object other$defaultServer = other.getDefaultServer(); + Label_0165: { + if (this$defaultServer == null) { + if (other$defaultServer == null) { + break Label_0165; + } + } else if (this$defaultServer.equals(other$defaultServer)) { + break Label_0165; + } + return false; + } + final Object this$fallbackServer = this.getFallbackServer(); + final Object other$fallbackServer = other.getFallbackServer(); + Label_0202: { + if (this$fallbackServer == null) { + if (other$fallbackServer == null) { + break Label_0202; + } + } else if (this$fallbackServer.equals(other$fallbackServer)) { + break Label_0202; + } + return false; + } + if (this.isForceDefault() != other.isForceDefault()) { + return false; + } + final Object this$forcedHosts = this.getForcedHosts(); + final Object other$forcedHosts = other.getForcedHosts(); + Label_0252: { + if (this$forcedHosts == null) { + if (other$forcedHosts == null) { + break Label_0252; + } + } else if (this$forcedHosts.equals(other$forcedHosts)) { + break Label_0252; + } + return false; + } + final Object this$texturePack = this.getTexturePack(); + final Object other$texturePack = other.getTexturePack(); + Label_0289: { + if (this$texturePack == null) { + if (other$texturePack == null) { + break Label_0289; + } + } else if (this$texturePack.equals(other$texturePack)) { + break Label_0289; + } + return false; + } + final Object this$tabList = this.getTabList(); + final Object other$tabList = other.getTabList(); + if (this$tabList == null) { + if (other$tabList == null) { + return true; + } + } else if (this$tabList.equals(other$tabList)) { + return true; + } + final Object this$getServerIcon = this.getServerIcon(); + final Object other$getServerIcon = other.getServerIcon(); + if (this$getServerIcon == null) { + if (other$getServerIcon == null) { + return true; + } + } else if (this$getServerIcon.equals(other$getServerIcon)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof ListenerInfo; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $host = this.getHost(); + result = result * 31 + (($host == null) ? 0 : $host.hashCode()); + final Object $motd = this.getMotd(); + result = result * 31 + (($motd == null) ? 0 : $motd.hashCode()); + result = result * 31 + this.getMaxPlayers(); + result = result * 31 + this.getTabListSize(); + final Object $defaultServer = this.getDefaultServer(); + result = result * 31 + (($defaultServer == null) ? 0 : $defaultServer.hashCode()); + final Object $fallbackServer = this.getFallbackServer(); + result = result * 31 + (($fallbackServer == null) ? 0 : $fallbackServer.hashCode()); + result = result * 31 + (this.isForceDefault() ? 1231 : 1237); + final Object $forcedHosts = this.getForcedHosts(); + result = result * 31 + (($forcedHosts == null) ? 0 : $forcedHosts.hashCode()); + final Object $texturePack = this.getTexturePack(); + result = result * 31 + (($texturePack == null) ? 0 : $texturePack.hashCode()); + final Object $tabList = this.getTabList(); + result = result * 31 + (($tabList == null) ? 0 : $tabList.hashCode()); + final Object $serverIconCache = this.getTabList(); + result = result * 31 + (($serverIconCache == null) ? 0 : $serverIconCache.hashCode()); + return result; + } + + @Override + public String toString() { + return "ListenerInfo(host=" + this.getHost() + ", motd=" + this.getMotd() + ", maxPlayers=" + this.getMaxPlayers() + ", tabListSize=" + this.getTabListSize() + ", defaultServer=" + this.getDefaultServer() + ", fallbackServer=" + + this.getFallbackServer() + ", forceDefault=" + this.isForceDefault() + ", websocket=" + this.isWebsocket() + ", forcedHosts=" + this.getForcedHosts() + ", texturePack=" + this.getTexturePack() + ", tabList=" + this.getTabList() + ")"; + } + + public boolean isWebsocket() { + return websocket; + } + + public boolean hasForwardedHeaders() { + return forwardIp; + } + + public String getForwardedIPHeader() { + return forwardIpHeader; + } + + public String getServerIcon() { + return serverIcon; + } + + public int[] getServerIconCache() { + if(!serverIconLoaded) { + if(serverIcon != null) { + int[] img = ServerIcon.createServerIcon(new File(serverIcon)); + if(img != null) { + System.arraycopy(img, 0, serverIconCache, 0, img.length); + serverIconSet = true; + }else { + serverIconSet = false; + } + }else { + serverIconSet = false; + } + serverIconLoaded = true; + } + return serverIconCache; + } + + public boolean isIconSet() { + getServerIconCache(); + return serverIconSet; + } + + public boolean isForwardIp() { + return forwardIp; + } + + public boolean isServerIconLoaded() { + return serverIconLoaded; + } + + public boolean isServerIconSet() { + return serverIconSet; + } + + public boolean isAllowMOTD() { + return allowMOTD; + } + + public boolean isAllowQuery() { + return allowQuery; + } + + public MOTDCacheConfiguration getCacheConfig() { + return cacheConfig; + } + + public WebSocketRateLimiter getRateLimitIP() { + return rateLimitIP; + } + + public WebSocketRateLimiter getRateLimitLogin() { + return rateLimitLogin; + } + + public WebSocketRateLimiter getRateLimitMOTD() { + return rateLimitMOTD; + } + + public WebSocketRateLimiter getRateLimitQuery() { + return rateLimitQuery; + } + +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/config/MOTDCacheConfiguration.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/MOTDCacheConfiguration.java new file mode 100644 index 0000000..96dab1f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/MOTDCacheConfiguration.java @@ -0,0 +1,21 @@ +package net.md_5.bungee.api.config; + +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; + } + + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/config/ServerInfo.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/ServerInfo.java new file mode 100644 index 0000000..66e85f6 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/ServerInfo.java @@ -0,0 +1,70 @@ +package net.md_5.bungee.api.config; + +import java.net.InetSocketAddress; +import java.util.Collection; +import net.md_5.bungee.api.Callback; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ServerPing; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +/** + * Class used to represent a server to connect to. + */ +public interface ServerInfo +{ + + /** + * Get the name of this server. + * + * @return the configured name for this server address + */ + String getName(); + + /** + * Gets the connectable host + port pair for this server. Implementations + * expect this to be used as the unique identifier per each instance of this + * class. + * + * @return the IP and port pair for this server + */ + InetSocketAddress getAddress(); + + /** + * Get the set of all players on this server. + * + * @return an unmodifiable collection of all players on this server + */ + Collection getPlayers(); + + /** + * Returns the MOTD which should be used when this server is a forced host. + * + * @return the motd + */ + + /** + * Whether the player can access this server. It will only return false when + * the player has no permission and this server is restricted. + * + * @param sender the player to check access for + * @return whether access is granted to this server + */ + boolean canAccess(CommandSender sender); + + /** + * Send data by any available means to this server. + * + * @param channel the channel to send this data via + * @param data the data to send + */ + void sendData(String channel, byte[] data); + + /** + * Asynchronously gets the current player count on this server. + * + * @param callback the callback to call when the count has been retrieved. + */ + void ping(Callback callback); + + String getRedirect(); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/config/TexturePackInfo.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/TexturePackInfo.java new file mode 100644 index 0000000..5e8ade5 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/config/TexturePackInfo.java @@ -0,0 +1,69 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.config; + +import java.beans.ConstructorProperties; + +public class TexturePackInfo { + private final String url; + private final int size; + + @ConstructorProperties({ "url", "size" }) + public TexturePackInfo(final String url, final int size) { + this.url = url; + this.size = size; + } + + public String getUrl() { + return this.url; + } + + public int getSize() { + return this.size; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof TexturePackInfo)) { + return false; + } + final TexturePackInfo other = (TexturePackInfo) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$url = this.getUrl(); + final Object other$url = other.getUrl(); + if (this$url == null) { + if (other$url == null) { + return this.getSize() == other.getSize(); + } + } else if (this$url.equals(other$url)) { + return this.getSize() == other.getSize(); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof TexturePackInfo; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $url = this.getUrl(); + result = result * 31 + (($url == null) ? 0 : $url.hashCode()); + result = result * 31 + this.getSize(); + return result; + } + + @Override + public String toString() { + return "TexturePackInfo(url=" + this.getUrl() + ", size=" + this.getSize() + ")"; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/ConnectedPlayer.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/ConnectedPlayer.java new file mode 100644 index 0000000..cf09a65 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/ConnectedPlayer.java @@ -0,0 +1,8 @@ +package net.md_5.bungee.api.connection; + +/** + * Represents a player physically connected to the world hosted on this server. + */ +public interface ConnectedPlayer extends ProxiedPlayer +{ +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/Connection.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/Connection.java new file mode 100644 index 0000000..f0a3f50 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/Connection.java @@ -0,0 +1,48 @@ +package net.md_5.bungee.api.connection; + +import java.net.InetSocketAddress; +import net.md_5.bungee.protocol.packet.DefinedPacket; + +/** + * A proxy connection is defined as a connection directly connected to a socket. + * It should expose information about the remote peer, however not be specific + * to a type of connection, whether server or player. + */ +public interface Connection +{ + + /** + * Gets the remote address of this connection. + * + * @return the remote address + */ + InetSocketAddress getAddress(); + + /** + * Disconnects this end of the connection for the specified reason. If this + * is an {@link ProxiedPlayer} the respective server connection will be + * closed too. + * + * @param reason the reason shown to the player / sent to the server on + * disconnect + */ + void disconnect(String reason); + + /** + * Get the unsafe methods of this class. + * + * @return the unsafe method interface + */ + Unsafe unsafe(); + + interface Unsafe + { + + /** + * Send a packet to this connection. + * + * @param packet the packet to send + */ + void sendPacket(DefinedPacket packet); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/PendingConnection.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/PendingConnection.java new file mode 100644 index 0000000..fba6e4f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/PendingConnection.java @@ -0,0 +1,39 @@ +package net.md_5.bungee.api.connection; + +import java.net.InetSocketAddress; +import net.md_5.bungee.api.config.ListenerInfo; + +/** + * Represents a user attempting to log into the proxy. + */ +public interface PendingConnection extends Connection +{ + + /** + * Get the requested username. + * + * @return the requested username, or null if not set + */ + String getName(); + + /** + * Get the numerical client version of the player attempting to log in. + * + * @return the protocol version of the remote client + */ + byte getVersion(); + + /** + * Get the requested virtual host that the client tried to connect to. + * + * @return request virtual host or null if invalid / not specified. + */ + InetSocketAddress getVirtualHost(); + + /** + * Get the listener that accepted this connection. + * + * @return the accepting listener + */ + ListenerInfo getListener(); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java new file mode 100644 index 0000000..6ef00b2 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java @@ -0,0 +1,102 @@ +package net.md_5.bungee.api.connection; + +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.tab.TabListHandler; + +/** + * Represents a player who's connection is being connected to somewhere else, + * whether it be a remote or embedded server. + */ +public interface ProxiedPlayer extends Connection, CommandSender +{ + + /** + * Gets this player's display name. + * + * @return the players current display name + */ + String getDisplayName(); + + /** + * Sets this players display name to be used as their nametag and tab list + * name. + * + * @param name the name to set + */ + void setDisplayName(String name); + + /** + * Connects / transfers this user to the specified connection, gracefully + * closing the current one. Depending on the implementation, this method + * might return before the user has been connected. + * + * @param target the new server to connect to + */ + void connect(ServerInfo target); + + /** + * Gets the server this player is connected to. + * + * @return the server this player is connected to + */ + Server getServer(); + + /** + * Gets the ping time between the proxy and this connection. + * + * @return the current ping time + */ + int getPing(); + + /** + * Send a plugin message to this player. + * + * @param channel the channel to send this data via + * @param data the data to send + */ + void sendData(String channel, byte[] data); + + /** + * Get the pending connection that belongs to this player. + * + * @return the pending connection that this player used + */ + PendingConnection getPendingConnection(); + + /** + * Make this player chat (say something), to the server he is currently on. + * + * @param message the message to say + */ + void chat(String message); + + /** + * Sets the new tab list for the user. At this stage it is not advisable to + * change after the user has logged in! + * + * @param list the new list + */ + void setTabList(TabListHandler list); + + /** + * Get the current tab list. + * + * @return the tab list in use by this user + */ + TabListHandler getTabList(); + + /** + * Get the server which this player will be sent to next time the log in. + * + * @return the server, or null if default + */ + ServerInfo getReconnectServer(); + + /** + * Set the server which this player will be sent to next time the log in. + * + * @param server the server to set + */ + void setReconnectServer(ServerInfo server); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/Server.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/Server.java new file mode 100644 index 0000000..06eb6d1 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/connection/Server.java @@ -0,0 +1,25 @@ +package net.md_5.bungee.api.connection; + +import net.md_5.bungee.api.config.ServerInfo; + +/** + * Represents a destination which this proxy might connect to. + */ +public interface Server extends Connection +{ + + /** + * Returns the basic information about this server. + * + * @return the {@link ServerInfo} for this server + */ + public ServerInfo getInfo(); + + /** + * Send data by any available means to this server. + * + * @param channel the channel to send this data via + * @param data the data to send + */ + public abstract void sendData(String channel, byte[] data); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/AsyncEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/AsyncEvent.java new file mode 100644 index 0000000..27eeeb1 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/AsyncEvent.java @@ -0,0 +1,160 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.event; + +import java.beans.ConstructorProperties; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import com.google.common.base.Preconditions; + +import net.md_5.bungee.api.Callback; +import net.md_5.bungee.api.plugin.Event; +import net.md_5.bungee.api.plugin.Plugin; + +public class AsyncEvent extends Event { + private final Callback done; + private final Set intents; + private final AtomicBoolean fired; + private final AtomicInteger latch; + + @Override + public void postCall() { + this.fired.set(true); + if (this.latch.get() == 0) { + this.done.done((T) this, null); + } + } + + public void registerIntent(final Plugin plugin) { + Preconditions.checkState(!this.fired.get(), "Event %s has already been fired", new Object[] { this }); + Preconditions.checkState(!this.intents.contains(plugin), "Plugin %s already registered intent for event %s", new Object[] { plugin, this }); + this.intents.add(plugin); + this.latch.incrementAndGet(); + } + + public void completeIntent(final Plugin plugin) { + Preconditions.checkState(this.intents.contains(plugin), "Plugin %s has not registered intent for event %s", new Object[] { plugin, this }); + this.intents.remove(plugin); + if (this.latch.decrementAndGet() == 0 && this.fired.get()) { + this.done.done((T) this, null); + } + } + + @ConstructorProperties({ "done" }) + public AsyncEvent(final Callback done) { + this.intents = Collections.newSetFromMap(new ConcurrentHashMap()); + this.fired = new AtomicBoolean(); + this.latch = new AtomicInteger(); + this.done = done; + } + + public Callback getDone() { + return this.done; + } + + public Set getIntents() { + return this.intents; + } + + public AtomicBoolean getFired() { + return this.fired; + } + + public AtomicInteger getLatch() { + return this.latch; + } + + @Override + public String toString() { + return "AsyncEvent(super=" + super.toString() + ", done=" + this.getDone() + ", intents=" + this.getIntents() + ", fired=" + this.getFired() + ", latch=" + this.getLatch() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof AsyncEvent)) { + return false; + } + final AsyncEvent other = (AsyncEvent) o; + if (!other.canEqual(this)) { + return false; + } + if (!super.equals(o)) { + return false; + } + final Object this$done = this.getDone(); + final Object other$done = other.getDone(); + Label_0075: { + if (this$done == null) { + if (other$done == null) { + break Label_0075; + } + } else if (this$done.equals(other$done)) { + break Label_0075; + } + return false; + } + final Object this$intents = this.getIntents(); + final Object other$intents = other.getIntents(); + Label_0112: { + if (this$intents == null) { + if (other$intents == null) { + break Label_0112; + } + } else if (this$intents.equals(other$intents)) { + break Label_0112; + } + return false; + } + final Object this$fired = this.getFired(); + final Object other$fired = other.getFired(); + Label_0149: { + if (this$fired == null) { + if (other$fired == null) { + break Label_0149; + } + } else if (this$fired.equals(other$fired)) { + break Label_0149; + } + return false; + } + final Object this$latch = this.getLatch(); + final Object other$latch = other.getLatch(); + if (this$latch == null) { + if (other$latch == null) { + return true; + } + } else if (this$latch.equals(other$latch)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof AsyncEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + super.hashCode(); + final Object $done = this.getDone(); + result = result * 31 + (($done == null) ? 0 : $done.hashCode()); + final Object $intents = this.getIntents(); + result = result * 31 + (($intents == null) ? 0 : $intents.hashCode()); + final Object $fired = this.getFired(); + result = result * 31 + (($fired == null) ? 0 : $fired.hashCode()); + final Object $latch = this.getLatch(); + result = result * 31 + (($latch == null) ? 0 : $latch.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ChatEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ChatEvent.java new file mode 100644 index 0000000..eef6d50 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ChatEvent.java @@ -0,0 +1,91 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.event; + +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.plugin.Cancellable; + +public class ChatEvent extends TargetedEvent implements Cancellable { + private boolean cancelled; + private String message; + + public ChatEvent(final Connection sender, final Connection receiver, final String message) { + super(sender, receiver); + this.message = message; + } + + public boolean isCommand() { + return this.message.length() > 0 && this.message.charAt(0) == '/'; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + public String getMessage() { + return this.message; + } + + @Override + public void setCancelled(final boolean cancelled) { + this.cancelled = cancelled; + } + + public void setMessage(final String message) { + this.message = message; + } + + @Override + public String toString() { + return "ChatEvent(super=" + super.toString() + ", cancelled=" + this.isCancelled() + ", message=" + this.getMessage() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof ChatEvent)) { + return false; + } + final ChatEvent other = (ChatEvent) o; + if (!other.canEqual(this)) { + return false; + } + if (!super.equals(o)) { + return false; + } + if (this.isCancelled() != other.isCancelled()) { + return false; + } + final Object this$message = this.getMessage(); + final Object other$message = other.getMessage(); + if (this$message == null) { + if (other$message == null) { + return true; + } + } else if (this$message.equals(other$message)) { + return true; + } + return false; + } + + @Override + public boolean canEqual(final Object other) { + return other instanceof ChatEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + super.hashCode(); + result = result * 31 + (this.isCancelled() ? 1231 : 1237); + final Object $message = this.getMessage(); + result = result * 31 + (($message == null) ? 0 : $message.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/LoginEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/LoginEvent.java new file mode 100644 index 0000000..c66d6df --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/LoginEvent.java @@ -0,0 +1,103 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.event; + +import net.md_5.bungee.api.Callback; +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.plugin.Cancellable; + +public class LoginEvent extends AsyncEvent implements Cancellable { + private boolean cancelled; + private String cancelReason; + private final PendingConnection connection; + + public LoginEvent(final PendingConnection connection, final Callback done) { + super(done); + this.connection = connection; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + public String getCancelReason() { + return this.cancelReason; + } + + public PendingConnection getConnection() { + return this.connection; + } + + @Override + public void setCancelled(final boolean cancelled) { + this.cancelled = cancelled; + } + + public void setCancelReason(final String cancelReason) { + this.cancelReason = cancelReason; + } + + @Override + public String toString() { + return "LoginEvent(cancelled=" + this.isCancelled() + ", cancelReason=" + this.getCancelReason() + ", connection=" + this.getConnection() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof LoginEvent)) { + return false; + } + final LoginEvent other = (LoginEvent) o; + if (!other.canEqual(this)) { + return false; + } + if (this.isCancelled() != other.isCancelled()) { + return false; + } + final Object this$cancelReason = this.getCancelReason(); + final Object other$cancelReason = other.getCancelReason(); + Label_0078: { + if (this$cancelReason == null) { + if (other$cancelReason == null) { + break Label_0078; + } + } else if (this$cancelReason.equals(other$cancelReason)) { + break Label_0078; + } + return false; + } + final Object this$connection = this.getConnection(); + final Object other$connection = other.getConnection(); + if (this$connection == null) { + if (other$connection == null) { + return true; + } + } else if (this$connection.equals(other$connection)) { + return true; + } + return false; + } + + @Override + public boolean canEqual(final Object other) { + return other instanceof LoginEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + (this.isCancelled() ? 1231 : 1237); + final Object $cancelReason = this.getCancelReason(); + result = result * 31 + (($cancelReason == null) ? 0 : $cancelReason.hashCode()); + final Object $connection = this.getConnection(); + result = result * 31 + (($connection == null) ? 0 : $connection.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PermissionCheckEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PermissionCheckEvent.java new file mode 100644 index 0000000..6943c88 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PermissionCheckEvent.java @@ -0,0 +1,96 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.event; + +import java.beans.ConstructorProperties; + +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Event; + +public class PermissionCheckEvent extends Event { + private final CommandSender sender; + private final String permission; + private boolean hasPermission; + + public boolean hasPermission() { + return this.hasPermission; + } + + public CommandSender getSender() { + return this.sender; + } + + public String getPermission() { + return this.permission; + } + + public void setHasPermission(final boolean hasPermission) { + this.hasPermission = hasPermission; + } + + @ConstructorProperties({ "sender", "permission", "hasPermission" }) + public PermissionCheckEvent(final CommandSender sender, final String permission, final boolean hasPermission) { + this.sender = sender; + this.permission = permission; + this.hasPermission = hasPermission; + } + + @Override + public String toString() { + return "PermissionCheckEvent(sender=" + this.getSender() + ", permission=" + this.getPermission() + ", hasPermission=" + this.hasPermission + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PermissionCheckEvent)) { + return false; + } + final PermissionCheckEvent other = (PermissionCheckEvent) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$sender = this.getSender(); + final Object other$sender = other.getSender(); + Label_0065: { + if (this$sender == null) { + if (other$sender == null) { + break Label_0065; + } + } else if (this$sender.equals(other$sender)) { + break Label_0065; + } + return false; + } + final Object this$permission = this.getPermission(); + final Object other$permission = other.getPermission(); + if (this$permission == null) { + if (other$permission == null) { + return this.hasPermission == other.hasPermission; + } + } else if (this$permission.equals(other$permission)) { + return this.hasPermission == other.hasPermission; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PermissionCheckEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $sender = this.getSender(); + result = result * 31 + (($sender == null) ? 0 : $sender.hashCode()); + final Object $permission = this.getPermission(); + result = result * 31 + (($permission == null) ? 0 : $permission.hashCode()); + result = result * 31 + (this.hasPermission ? 1231 : 1237); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PlayerDisconnectEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PlayerDisconnectEvent.java new file mode 100644 index 0000000..8e4a727 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PlayerDisconnectEvent.java @@ -0,0 +1,65 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.event; + +import java.beans.ConstructorProperties; + +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Event; + +public class PlayerDisconnectEvent extends Event { + private final ProxiedPlayer player; + + @ConstructorProperties({ "player" }) + public PlayerDisconnectEvent(final ProxiedPlayer player) { + this.player = player; + } + + public ProxiedPlayer getPlayer() { + return this.player; + } + + @Override + public String toString() { + return "PlayerDisconnectEvent(player=" + this.getPlayer() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PlayerDisconnectEvent)) { + return false; + } + final PlayerDisconnectEvent other = (PlayerDisconnectEvent) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$player = this.getPlayer(); + final Object other$player = other.getPlayer(); + if (this$player == null) { + if (other$player == null) { + return true; + } + } else if (this$player.equals(other$player)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PlayerDisconnectEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $player = this.getPlayer(); + result = result * 31 + (($player == null) ? 0 : $player.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PlayerHandshakeEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PlayerHandshakeEvent.java new file mode 100644 index 0000000..89349e2 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PlayerHandshakeEvent.java @@ -0,0 +1,28 @@ +package net.md_5.bungee.api.event; + +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.protocol.packet.Packet2Handshake; +import net.md_5.bungee.api.plugin.Event; + +/** + * Event called to represent a player first making their presence and username + * known. + */ +public class PlayerHandshakeEvent extends Event +{ + + /** + * Connection attempting to login. + */ + private final PendingConnection connection; + /** + * The handshake. + */ + private final Packet2Handshake handshake; + + public PlayerHandshakeEvent(PendingConnection connection, Packet2Handshake handshake) + { + this.connection = connection; + this.handshake = handshake; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PluginMessageEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PluginMessageEvent.java new file mode 100644 index 0000000..de3f3e1 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PluginMessageEvent.java @@ -0,0 +1,92 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.event; + +import java.util.Arrays; + +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.plugin.Cancellable; + +public class PluginMessageEvent extends TargetedEvent implements Cancellable { + private boolean cancelled; + private final String tag; + private final byte[] data; + + public PluginMessageEvent(final Connection sender, final Connection receiver, final String tag, final byte[] data) { + super(sender, receiver); + this.tag = tag; + this.data = data; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + public String getTag() { + return this.tag; + } + + public byte[] getData() { + return this.data; + } + + @Override + public void setCancelled(final boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public String toString() { + return "PluginMessageEvent(super=" + super.toString() + ", cancelled=" + this.isCancelled() + ", tag=" + this.getTag() + ", data=" + Arrays.toString(this.getData()) + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PluginMessageEvent)) { + return false; + } + final PluginMessageEvent other = (PluginMessageEvent) o; + if (!other.canEqual(this)) { + return false; + } + if (!super.equals(o)) { + return false; + } + if (this.isCancelled() != other.isCancelled()) { + return false; + } + final Object this$tag = this.getTag(); + final Object other$tag = other.getTag(); + if (this$tag == null) { + if (other$tag == null) { + return Arrays.equals(this.getData(), other.getData()); + } + } else if (this$tag.equals(other$tag)) { + return Arrays.equals(this.getData(), other.getData()); + } + return false; + } + + //@Override + public boolean canEqual(final Object other) { + return other instanceof PluginMessageEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + super.hashCode(); + result = result * 31 + (this.isCancelled() ? 1231 : 1237); + final Object $tag = this.getTag(); + result = result * 31 + (($tag == null) ? 0 : $tag.hashCode()); + result = result * 31 + Arrays.hashCode(this.getData()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PostLoginEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PostLoginEvent.java new file mode 100644 index 0000000..d78b53a --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/PostLoginEvent.java @@ -0,0 +1,61 @@ +package net.md_5.bungee.api.event; + +import java.beans.ConstructorProperties; + +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Event; + +public class PostLoginEvent extends Event { + private final ProxiedPlayer player; + + @ConstructorProperties({ "player" }) + public PostLoginEvent(final ProxiedPlayer player) { + this.player = player; + } + + public ProxiedPlayer getPlayer() { + return this.player; + } + + @Override + public String toString() { + return "PostLoginEvent(player=" + this.getPlayer() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PostLoginEvent)) { + return false; + } + final PostLoginEvent other = (PostLoginEvent) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$player = this.getPlayer(); + final Object other$player = other.getPlayer(); + if (this$player == null) { + if (other$player == null) { + return true; + } + } else if (this$player.equals(other$player)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PostLoginEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $player = this.getPlayer(); + result = result * 31 + (($player == null) ? 0 : $player.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ProxyPingEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ProxyPingEvent.java new file mode 100644 index 0000000..a4052e0 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ProxyPingEvent.java @@ -0,0 +1,90 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.event; + +import java.beans.ConstructorProperties; + +import net.md_5.bungee.api.ServerPing; +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.plugin.Event; + +public class ProxyPingEvent extends Event { + private final PendingConnection connection; + private ServerPing response; + + public PendingConnection getConnection() { + return this.connection; + } + + public ServerPing getResponse() { + return this.response; + } + + public void setResponse(final ServerPing response) { + this.response = response; + } + + @ConstructorProperties({ "connection", "response" }) + public ProxyPingEvent(final PendingConnection connection, final ServerPing response) { + this.connection = connection; + this.response = response; + } + + @Override + public String toString() { + return "ProxyPingEvent(connection=" + this.getConnection() + ", response=" + this.getResponse() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof ProxyPingEvent)) { + return false; + } + final ProxyPingEvent other = (ProxyPingEvent) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$connection = this.getConnection(); + final Object other$connection = other.getConnection(); + Label_0065: { + if (this$connection == null) { + if (other$connection == null) { + break Label_0065; + } + } else if (this$connection.equals(other$connection)) { + break Label_0065; + } + return false; + } + final Object this$response = this.getResponse(); + final Object other$response = other.getResponse(); + if (this$response == null) { + if (other$response == null) { + return true; + } + } else if (this$response.equals(other$response)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof ProxyPingEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $connection = this.getConnection(); + result = result * 31 + (($connection == null) ? 0 : $connection.hashCode()); + final Object $response = this.getResponse(); + result = result * 31 + (($response == null) ? 0 : $response.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java new file mode 100644 index 0000000..22b7aca --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java @@ -0,0 +1,100 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.event; + +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Cancellable; +import net.md_5.bungee.api.plugin.Event; + +public class ServerConnectEvent extends Event implements Cancellable { + private final ProxiedPlayer player; + private ServerInfo target; + private boolean cancelled; + + public ServerConnectEvent(final ProxiedPlayer player, final ServerInfo target) { + this.player = player; + this.target = target; + } + + public ProxiedPlayer getPlayer() { + return this.player; + } + + public ServerInfo getTarget() { + return this.target; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + public void setTarget(final ServerInfo target) { + this.target = target; + } + + @Override + public void setCancelled(final boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public String toString() { + return "ServerConnectEvent(player=" + this.getPlayer() + ", target=" + this.getTarget() + ", cancelled=" + this.isCancelled() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof ServerConnectEvent)) { + return false; + } + final ServerConnectEvent other = (ServerConnectEvent) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$player = this.getPlayer(); + final Object other$player = other.getPlayer(); + Label_0065: { + if (this$player == null) { + if (other$player == null) { + break Label_0065; + } + } else if (this$player.equals(other$player)) { + break Label_0065; + } + return false; + } + final Object this$target = this.getTarget(); + final Object other$target = other.getTarget(); + if (this$target == null) { + if (other$target == null) { + return this.isCancelled() == other.isCancelled(); + } + } else if (this$target.equals(other$target)) { + return this.isCancelled() == other.isCancelled(); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof ServerConnectEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $player = this.getPlayer(); + result = result * 31 + (($player == null) ? 0 : $player.hashCode()); + final Object $target = this.getTarget(); + result = result * 31 + (($target == null) ? 0 : $target.hashCode()); + result = result * 31 + (this.isCancelled() ? 1231 : 1237); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerConnectedEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerConnectedEvent.java new file mode 100644 index 0000000..937be35 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerConnectedEvent.java @@ -0,0 +1,86 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.event; + +import java.beans.ConstructorProperties; + +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Server; +import net.md_5.bungee.api.plugin.Event; + +public class ServerConnectedEvent extends Event { + private final ProxiedPlayer player; + private final Server server; + + @ConstructorProperties({ "player", "server" }) + public ServerConnectedEvent(final ProxiedPlayer player, final Server server) { + this.player = player; + this.server = server; + } + + public ProxiedPlayer getPlayer() { + return this.player; + } + + public Server getServer() { + return this.server; + } + + @Override + public String toString() { + return "ServerConnectedEvent(player=" + this.getPlayer() + ", server=" + this.getServer() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof ServerConnectedEvent)) { + return false; + } + final ServerConnectedEvent other = (ServerConnectedEvent) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$player = this.getPlayer(); + final Object other$player = other.getPlayer(); + Label_0065: { + if (this$player == null) { + if (other$player == null) { + break Label_0065; + } + } else if (this$player.equals(other$player)) { + break Label_0065; + } + return false; + } + final Object this$server = this.getServer(); + final Object other$server = other.getServer(); + if (this$server == null) { + if (other$server == null) { + return true; + } + } else if (this$server.equals(other$server)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof ServerConnectedEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $player = this.getPlayer(); + result = result * 31 + (($player == null) ? 0 : $player.hashCode()); + final Object $server = this.getServer(); + result = result * 31 + (($server == null) ? 0 : $server.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerKickEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerKickEvent.java new file mode 100644 index 0000000..89c8cea --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerKickEvent.java @@ -0,0 +1,143 @@ +package net.md_5.bungee.api.event; + +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Cancellable; +import net.md_5.bungee.api.plugin.Event; + +/** + * Represents a player getting kicked from a server. + */ + +public class ServerKickEvent extends Event implements Cancellable +{ + + private boolean cancelled; + private final ProxiedPlayer player; + private String kickReason; + private ServerInfo cancelServer; + private State state; + + public enum State + { + + CONNECTING, CONNECTED, UNKNOWN; + } + + public ServerKickEvent(ProxiedPlayer player, String kickReason, ServerInfo cancelServer) + { + this( player, kickReason, cancelServer, State.UNKNOWN ); + } + + public ServerKickEvent(ProxiedPlayer player, String kickReason, ServerInfo cancelServer, State state) + { + this.player = player; + this.kickReason = kickReason; + this.cancelServer = cancelServer; + this.state = state; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + public ProxiedPlayer getPlayer() { + return this.player; + } + + public String getKickReason() { + return this.kickReason; + } + + public ServerInfo getCancelServer() { + return this.cancelServer; + } + + @Override + public void setCancelled(final boolean cancelled) { + this.cancelled = cancelled; + } + + public void setKickReason(final String kickReason) { + this.kickReason = kickReason; + } + + public void setCancelServer(final ServerInfo cancelServer) { + this.cancelServer = cancelServer; + } + + @Override + public String toString() { + return "ServerKickEvent(cancelled=" + this.isCancelled() + ", player=" + this.getPlayer() + ", kickReason=" + this.getKickReason() + ", cancelServer=" + this.getCancelServer() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof ServerKickEvent)) { + return false; + } + final ServerKickEvent other = (ServerKickEvent) o; + if (!other.canEqual(this)) { + return false; + } + if (this.isCancelled() != other.isCancelled()) { + return false; + } + final Object this$player = this.getPlayer(); + final Object other$player = other.getPlayer(); + Label_0078: { + if (this$player == null) { + if (other$player == null) { + break Label_0078; + } + } else if (this$player.equals(other$player)) { + break Label_0078; + } + return false; + } + final Object this$kickReason = this.getKickReason(); + final Object other$kickReason = other.getKickReason(); + Label_0115: { + if (this$kickReason == null) { + if (other$kickReason == null) { + break Label_0115; + } + } else if (this$kickReason.equals(other$kickReason)) { + break Label_0115; + } + return false; + } + final Object this$cancelServer = this.getCancelServer(); + final Object other$cancelServer = other.getCancelServer(); + if (this$cancelServer == null) { + if (other$cancelServer == null) { + return true; + } + } else if (this$cancelServer.equals(other$cancelServer)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof ServerKickEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + (this.isCancelled() ? 1231 : 1237); + final Object $player = this.getPlayer(); + result = result * 31 + (($player == null) ? 0 : $player.hashCode()); + final Object $kickReason = this.getKickReason(); + result = result * 31 + (($kickReason == null) ? 0 : $kickReason.hashCode()); + final Object $cancelServer = this.getCancelServer(); + result = result * 31 + (($cancelServer == null) ? 0 : $cancelServer.hashCode()); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerSwitchEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerSwitchEvent.java new file mode 100644 index 0000000..ce3753a --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/ServerSwitchEvent.java @@ -0,0 +1,65 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.event; + +import java.beans.ConstructorProperties; + +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Event; + +public class ServerSwitchEvent extends Event { + private final ProxiedPlayer player; + + @ConstructorProperties({ "player" }) + public ServerSwitchEvent(final ProxiedPlayer player) { + this.player = player; + } + + public ProxiedPlayer getPlayer() { + return this.player; + } + + @Override + public String toString() { + return "ServerSwitchEvent(player=" + this.getPlayer() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof ServerSwitchEvent)) { + return false; + } + final ServerSwitchEvent other = (ServerSwitchEvent) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$player = this.getPlayer(); + final Object other$player = other.getPlayer(); + if (this$player == null) { + if (other$player == null) { + return true; + } + } else if (this$player.equals(other$player)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof ServerSwitchEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $player = this.getPlayer(); + result = result * 31 + (($player == null) ? 0 : $player.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/TargetedEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/TargetedEvent.java new file mode 100644 index 0000000..07d7028 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/TargetedEvent.java @@ -0,0 +1,85 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.event; + +import java.beans.ConstructorProperties; + +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.plugin.Event; + +public abstract class TargetedEvent extends Event { + private final Connection sender; + private final Connection receiver; + + public Connection getSender() { + return this.sender; + } + + public Connection getReceiver() { + return this.receiver; + } + + @Override + public String toString() { + return "TargetedEvent(sender=" + this.getSender() + ", receiver=" + this.getReceiver() + ")"; + } + + @ConstructorProperties({ "sender", "receiver" }) + public TargetedEvent(final Connection sender, final Connection receiver) { + this.sender = sender; + this.receiver = receiver; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof TargetedEvent)) { + return false; + } + final TargetedEvent other = (TargetedEvent) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$sender = this.getSender(); + final Object other$sender = other.getSender(); + Label_0065: { + if (this$sender == null) { + if (other$sender == null) { + break Label_0065; + } + } else if (this$sender.equals(other$sender)) { + break Label_0065; + } + return false; + } + final Object this$receiver = this.getReceiver(); + final Object other$receiver = other.getReceiver(); + if (this$receiver == null) { + if (other$receiver == null) { + return true; + } + } else if (this$receiver.equals(other$receiver)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof TargetedEvent; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $sender = this.getSender(); + result = result * 31 + (($sender == null) ? 0 : $sender.hashCode()); + final Object $receiver = this.getReceiver(); + result = result * 31 + (($receiver == null) ? 0 : $receiver.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/WebsocketMOTDEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/WebsocketMOTDEvent.java new file mode 100644 index 0000000..3c665f0 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/WebsocketMOTDEvent.java @@ -0,0 +1,15 @@ +package net.md_5.bungee.api.event; + +import net.md_5.bungee.api.MOTD; + +public class WebsocketMOTDEvent extends WebsocketQueryEvent { + + public WebsocketMOTDEvent(MOTD connection) { + super(connection); + } + + public MOTD getMOTD() { + return (MOTD)connection; + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/event/WebsocketQueryEvent.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/WebsocketQueryEvent.java new file mode 100644 index 0000000..52ba23b --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/event/WebsocketQueryEvent.java @@ -0,0 +1,33 @@ +package net.md_5.bungee.api.event; + +import java.net.InetAddress; + +import net.md_5.bungee.api.QueryConnection; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.plugin.Event; + +public class WebsocketQueryEvent extends Event { + + protected final QueryConnection connection; + + public WebsocketQueryEvent(QueryConnection connection) { + this.connection = connection; + } + + public InetAddress getRemoteAddress() { + return connection.getRemoteAddress(); + } + + public ListenerInfo getListener() { + return connection.getListener(); + } + + public String getAccept() { + return connection.getAccept(); + } + + public QueryConnection getQuery() { + return connection; + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Cancellable.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Cancellable.java new file mode 100644 index 0000000..2b04a2b --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Cancellable.java @@ -0,0 +1,23 @@ +package net.md_5.bungee.api.plugin; + +/** + * Events that implement this indicate that they may be cancelled and thus + * prevented from happening. + */ +public interface Cancellable +{ + + /** + * Get whether or not this event is cancelled. + * + * @return the cancelled state of this event + */ + public boolean isCancelled(); + + /** + * Sets the cancelled state of this event. + * + * @param cancel the state to set + */ + public void setCancelled(boolean cancel); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Command.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Command.java new file mode 100644 index 0000000..67494bc --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Command.java @@ -0,0 +1,99 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.plugin; + +import java.util.Arrays; + +import com.google.common.base.Preconditions; + +import net.md_5.bungee.api.CommandSender; + +public abstract class Command { + private final String name; + private final String permission; + private final String[] aliases; + + public Command(final String name) { + this(name, null, new String[0]); + } + + public Command(final String name, final String permission, final String... aliases) { + Preconditions.checkArgument(name != null, (Object) "name"); + this.name = name; + this.permission = permission; + this.aliases = aliases; + } + + public abstract void execute(final CommandSender p0, final String[] p1); + + public String getName() { + return this.name; + } + + public String getPermission() { + return this.permission; + } + + public String[] getAliases() { + return this.aliases; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Command)) { + return false; + } + final Command other = (Command) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$name = this.getName(); + final Object other$name = other.getName(); + Label_0065: { + if (this$name == null) { + if (other$name == null) { + break Label_0065; + } + } else if (this$name.equals(other$name)) { + break Label_0065; + } + return false; + } + final Object this$permission = this.getPermission(); + final Object other$permission = other.getPermission(); + if (this$permission == null) { + if (other$permission == null) { + return Arrays.deepEquals(this.getAliases(), other.getAliases()); + } + } else if (this$permission.equals(other$permission)) { + return Arrays.deepEquals(this.getAliases(), other.getAliases()); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof Command; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * 31 + (($name == null) ? 0 : $name.hashCode()); + final Object $permission = this.getPermission(); + result = result * 31 + (($permission == null) ? 0 : $permission.hashCode()); + result = result * 31 + Arrays.deepHashCode(this.getAliases()); + return result; + } + + @Override + public String toString() { + return "Command(name=" + this.getName() + ", permission=" + this.getPermission() + ", aliases=" + Arrays.deepToString(this.getAliases()) + ")"; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Event.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Event.java new file mode 100644 index 0000000..e2e01ec --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Event.java @@ -0,0 +1,15 @@ +package net.md_5.bungee.api.plugin; + +/** + * Dummy class which all callable events must extend. + */ +public abstract class Event +{ + + /** + * Method called after this event has been dispatched to all handlers. + */ + public void postCall() + { + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Listener.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Listener.java new file mode 100644 index 0000000..31ed4ee --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Listener.java @@ -0,0 +1,8 @@ +package net.md_5.bungee.api.plugin; + +/** + * Dummy interface which all event subscribers and listeners must implement. + */ +public interface Listener +{ +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Plugin.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Plugin.java new file mode 100644 index 0000000..2b66e1b --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/Plugin.java @@ -0,0 +1,101 @@ +package net.md_5.bungee.api.plugin; + +import java.io.File; +import java.io.InputStream; +import java.util.logging.Logger; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ConfigurationAdapter; + +/** + * Represents any Plugin that may be loaded at runtime to enhance existing + * functionality. + */ +public class Plugin +{ + private PluginDescription description; + private ProxyServer proxy; + private File file; + private Logger logger; + + protected Plugin() { + } + + protected Plugin(PluginDescription forceDesc) { + this.description = forceDesc; + } + + public PluginDescription getDescription() { + return description; + } + + public ProxyServer getProxy() { + return proxy; + } + + public File getFile() { + return file; + } + + public Logger getLogger() { + return logger; + } + + /** + * Called when the plugin has just been loaded. Most of the proxy will not + * be initialized, so only use it for registering + * {@link ConfigurationAdapter}'s and other predefined behavior. + */ + public void onLoad() + { + } + + /** + * Called when this plugin is enabled. + */ + public void onEnable() + { + } + + /** + * Called when this plugin is disabled. + */ + public void onDisable() + { + } + + /** + * Gets the data folder where this plugin may store arbitrary data. It will + * be a child of {@link ProxyServer#getPluginsFolder()}. + * + * @return the data folder of this plugin + */ + public final File getDataFolder() + { + return new File( getProxy().getPluginsFolder(), getDescription().getName() ); + } + + /** + * Get a resource from within this plugins jar or container. Care must be + * taken to close the returned stream. + * + * @param name the full path name of this resource + * @return the stream for getting this resource, or null if it does not + * exist + */ + public final InputStream getResourceAsStream(String name) + { + return getClass().getClassLoader().getResourceAsStream( name ); + } + + /** + * Called by the loader to initialize the fields in this plugin. + * + */ + final void init(ProxyServer proxy, PluginDescription description) + { + this.proxy = proxy; + this.description = description; + this.file = description.getFile(); + this.logger = new PluginLogger( this ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginClassloader.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginClassloader.java new file mode 100644 index 0000000..85203fb --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginClassloader.java @@ -0,0 +1,55 @@ +package net.md_5.bungee.api.plugin; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +public class PluginClassloader extends URLClassLoader +{ + + private static final Set allLoaders = new CopyOnWriteArraySet<>(); + + static + { + ClassLoader.registerAsParallelCapable(); + } + + public PluginClassloader(URL[] urls) + { + super( urls ); + allLoaders.add( this ); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException + { + return loadClass0( name, resolve, true ); + } + + private Class loadClass0(String name, boolean resolve, boolean checkOther) throws ClassNotFoundException + { + try + { + return super.loadClass( name, resolve ); + } catch ( ClassNotFoundException ex ) + { + } + if ( checkOther ) + { + for ( PluginClassloader loader : allLoaders ) + { + if ( loader != this ) + { + try + { + return loader.loadClass0( name, resolve, false ); + } catch ( ClassNotFoundException ex ) + { + } + } + } + } + throw new ClassNotFoundException( name ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginDescription.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginDescription.java new file mode 100644 index 0000000..5589a37 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginDescription.java @@ -0,0 +1,99 @@ +package net.md_5.bungee.api.plugin; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; +/** + * POJO representing the plugin.yml file. + */ + +//@Data + +public class PluginDescription +{ + public PluginDescription(){ + + } + + public PluginDescription(String name, String main, String version, String author, Set depends, File file) { + this.name = name; + this.main = main; + this.version = version; + this.author = author; + this.depends = depends; + this.file = file; + } + + public String getName() { + return name; + } + + public String getMain() { + return main; + } + + public String getVersion() { + return version; + } + + public String getAuthor() { + return author; + } + + public Set getDepends() { + return depends; + } + + public void setName(String name) { + this.name = name; + } + + public void setMain(String main) { + this.main = main; + } + + public void setVersion(String version) { + this.version = version; + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setDepends(Set depends) { + this.depends = depends; + } + + public void setFile(File file) { + this.file = file; + } + + /** + * Friendly name of the plugin. + */ + private String name; + /** + * Plugin main class. Needs to extend {@link Plugin}. + */ + private String main; + /** + * Plugin version. + */ + private String version; + /** + * Plugin author. + */ + private String author; + /** + * Plugin hard dependencies. + */ + private Set depends = new HashSet<>(); + /** + * File we were loaded from. + */ + private File file = null; + + public File getFile() { + return file; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginLogger.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginLogger.java new file mode 100644 index 0000000..118fb71 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginLogger.java @@ -0,0 +1,24 @@ +package net.md_5.bungee.api.plugin; + +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import net.md_5.bungee.api.ProxyServer; + +public class PluginLogger extends Logger +{ + + private String pluginName; + + protected PluginLogger(Plugin plugin) + { + super( plugin.getClass().getCanonicalName(), null ); + pluginName = "[" + plugin.getDescription().getName() + "] "; + } + + @Override + public void log(LogRecord logRecord) + { + logRecord.setMessage( pluginName + logRecord.getMessage() ); + ProxyServer.getInstance().getLogger().log( logRecord ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java new file mode 100644 index 0000000..c0cd4f4 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java @@ -0,0 +1,398 @@ +package net.md_5.bungee.api.plugin; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import com.google.common.eventbus.Subscribe; +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.regex.Pattern; +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.event.EventBus; +import net.md_5.bungee.event.EventHandler; +import org.yaml.snakeyaml.Yaml; + +/** + * Class to manage bridging between plugin duties and implementation duties, for + * example event handling and plugin management. + */ +public class PluginManager +{ + + private static final Pattern argsSplit = Pattern.compile( " " ); + /*========================================================================*/ + private final ProxyServer proxy; + /*========================================================================*/ + private final Yaml yaml = new Yaml(); + private final EventBus eventBus; + private final Map plugins = new LinkedHashMap<>(); + private final Map commandMap = new HashMap<>(); + private Map toLoad = new HashMap<>(); + private Multimap commandsByPlugin = ArrayListMultimap.create(); + private Multimap listenersByPlugin = ArrayListMultimap.create(); + + @SuppressWarnings("unchecked") + public PluginManager(ProxyServer proxy) + { + this.proxy = proxy; + eventBus = new EventBus( proxy.getLogger() ); + } + + /** + * Register a command so that it may be executed. + * + * @param plugin the plugin owning this command + * @param command the command to register + */ + public void registerCommand(Plugin plugin, Command command) + { + commandMap.put( command.getName().toLowerCase(), command ); + for ( String alias : command.getAliases() ) + { + commandMap.put( alias.toLowerCase(), command ); + } + commandsByPlugin.put( plugin, command ); + } + + /** + * Unregister a command so it will no longer be executed. + * + * @param command the command to unregister + */ + public void unregisterCommand(Command command) + { + commandMap.values().remove( command ); + commandsByPlugin.values().remove( command ); + } + + /** + * Unregister all commands owned by a {@link Plugin} + * + * @param plugin the plugin to register the commands of + */ + public void unregisterCommands(Plugin plugin) + { + for ( Iterator it = commandsByPlugin.get( plugin ).iterator(); it.hasNext(); ) + { + commandMap.values().remove( it.next() ); + it.remove(); + } + } + + public boolean dispatchCommand(CommandSender sender, String commandLine) + { + return dispatchCommand( sender, commandLine, null ); + } + + /** + * Execute a command if it is registered, else return false. + * + * @param sender the sender executing the command + * @param commandLine the complete command line including command name and + * arguments + * @return whether the command was handled + */ + public boolean dispatchCommand(CommandSender sender, String commandLine, List tabResults) + { + String[] split = argsSplit.split( commandLine ); + // Check for chat that only contains " " + if ( split.length == 0 ) + { + return false; + } + + String commandName = split[0].toLowerCase(); + if ( proxy.getDisabledCommands().contains( commandName ) ) + { + return false; + } + Command command = commandMap.get( commandName ); + if ( command == null ) + { + return false; + } + + String permission = command.getPermission(); + if ( permission != null && !permission.isEmpty() && !sender.hasPermission( permission ) ) + { + sender.sendMessage( proxy.getTranslation( "no_permission" ) ); + return true; + } + + String[] args = Arrays.copyOfRange( split, 1, split.length ); + try + { + if ( tabResults == null ) + { + command.execute( sender, args ); + } else if ( command instanceof TabExecutor ) + { + for ( String s : ( (TabExecutor) command ).onTabComplete( sender, args ) ) + { + tabResults.add( s ); + } + } + } catch ( Exception ex ) + { + sender.sendMessage( ChatColor.RED + "An internal error occurred whilst executing this command, please check the console log for details." ); + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Error in dispatching command", ex ); + } + return true; + } + + /** + * Returns the {@link Plugin} objects corresponding to all loaded plugins. + * + * @return the set of loaded plugins + */ + public Collection getPlugins() + { + return plugins.values(); + } + + /** + * Returns a loaded plugin identified by the specified name. + * + * @param name of the plugin to retrieve + * @return the retrieved plugin or null if not loaded + */ + public Plugin getPlugin(String name) + { + return plugins.get( name ); + } + + public void loadAndEnablePlugins() + { + Map pluginStatuses = new HashMap<>(); + for ( Map.Entry entry : toLoad.entrySet() ) + { + PluginDescription plugin = entry.getValue(); + if ( !enablePlugin( pluginStatuses, new Stack(), plugin ) ) + { + ProxyServer.getInstance().getLogger().warning( "Failed to enable " + entry.getKey() ); + } + } + toLoad.clear(); + toLoad = null; + + for ( Plugin plugin : plugins.values() ) + { + try + { + plugin.onEnable(); + ProxyServer.getInstance().getLogger().log( Level.INFO, "Enabled plugin {0} version {1} by {2}", new Object[] + { + plugin.getDescription().getName(), plugin.getDescription().getVersion(), plugin.getDescription().getAuthor() + } ); + } catch ( Throwable t ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Exception encountered when loading plugin: " + plugin.getDescription().getName(), t ); + } + } + } + + private boolean enablePlugin(Map pluginStatuses, Stack dependStack, PluginDescription plugin) + { + if ( pluginStatuses.containsKey( plugin ) ) + { + return pluginStatuses.get( plugin ); + } + + // success status + boolean status = true; + + // try to load dependencies first + for ( String dependName : plugin.getDepends() ) + { + PluginDescription depend = toLoad.get( dependName ); + Boolean dependStatus = ( depend != null ) ? pluginStatuses.get( depend ) : Boolean.FALSE; + + if ( dependStatus == null ) + { + if ( dependStack.contains( depend ) ) + { + StringBuilder dependencyGraph = new StringBuilder(); + for ( PluginDescription element : dependStack ) + { + dependencyGraph.append( element.getName() ).append( " -> " ); + } + dependencyGraph.append( plugin.getName() ).append( " -> " ).append( dependName ); + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Circular dependency detected: " + dependencyGraph ); + status = false; + } else + { + dependStack.push( plugin ); + dependStatus = this.enablePlugin( pluginStatuses, dependStack, depend ); + dependStack.pop(); + } + } + + if ( dependStatus == Boolean.FALSE ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, "{0} (required by {1}) is unavailable", new Object[] + { + String.valueOf( depend.getName() ), plugin.getName() + } ); + status = false; + } + + if ( !status ) + { + break; + } + } + + // do actual loading + if ( status ) + { + try + { + URLClassLoader loader = new PluginClassloader( new URL[] + { + plugin.getFile().toURI().toURL() + } ); + Class main = loader.loadClass( plugin.getMain() ); + Plugin clazz = (Plugin) main.getDeclaredConstructor().newInstance(); + + clazz.init( proxy, plugin ); + plugins.put( plugin.getName(), clazz ); + clazz.onLoad(); + ProxyServer.getInstance().getLogger().log( Level.INFO, "Loaded plugin {0} version {1} by {2}", new Object[] + { + plugin.getName(), plugin.getVersion(), plugin.getAuthor() + } ); + } catch ( Throwable t ) + { + proxy.getLogger().log( Level.WARNING, "Error enabling plugin " + plugin.getName(), t ); + } + } + + pluginStatuses.put( plugin, status ); + return status; + } + + public void addInternalPlugin(Plugin plug) { + this.plugins.put(plug.getDescription().getName(), plug); + plug.init(proxy, plug.getDescription()); + } + + /** + * Load all plugins from the specified folder. + * + * @param folder the folder to search for plugins in + */ + public void detectPlugins(File folder) + { + Preconditions.checkNotNull( folder, "folder" ); + Preconditions.checkArgument( folder.isDirectory(), "Must load from a directory" ); + + for ( File file : folder.listFiles() ) + { + if ( file.isFile() && file.getName().endsWith( ".jar" ) ) + { + try ( JarFile jar = new JarFile( file ) ) + { + JarEntry pdf = jar.getJarEntry( "plugin.yml" ); + Preconditions.checkNotNull( pdf, "Plugin must have a plugin.yml" ); + + try ( InputStream in = jar.getInputStream( pdf ) ) + { + PluginDescription desc = yaml.loadAs( in, PluginDescription.class ); + desc.setFile( file ); + toLoad.put( desc.getName(), desc ); + } + } catch ( Exception ex ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Could not load plugin from file " + file, ex ); + } + } + } + } + + /** + * Dispatch an event to all subscribed listeners and return the event once + * it has been handled by these listeners. + * + * @param the type bounds, must be a class which extends event + * @param event the event to call + * @return the called event + */ + public T callEvent(T event) + { + Preconditions.checkNotNull( event, "event" ); + + long start = System.nanoTime(); + eventBus.post( event ); + event.postCall(); + + long elapsed = start - System.nanoTime(); + if ( elapsed > 250000 ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Event {0} took more {1}ns to process!", new Object[] + { + event, elapsed + } ); + } + return event; + } + + /** + * Register a {@link Listener} for receiving called events. Methods in this + * Object which wish to receive events must be annotated with the + * {@link EventHandler} annotation. + * + * @param plugin the owning plugin + * @param listener the listener to register events for + */ + public void registerListener(Plugin plugin, Listener listener) + { + for ( Method method : listener.getClass().getDeclaredMethods() ) + { + Preconditions.checkArgument( !method.isAnnotationPresent( Subscribe.class ), + "Listener %s has registered using deprecated subscribe annotation! Please update to @EventHandler.", listener ); + } + eventBus.register( listener ); + listenersByPlugin.put( plugin, listener ); + } + + /** + * Unregister a {@link Listener} so that the events do not reach it anymore. + * + * @param listener the listener to unregister + */ + public void unregisterListener(Listener listener) + { + eventBus.unregister( listener ); + listenersByPlugin.values().remove( listener ); + } + + /** + * Unregister all of a Plugin's listener. + * + * @param plugin + */ + public void unregisterListeners(Plugin plugin) + { + for ( Iterator it = listenersByPlugin.get( plugin ).iterator(); it.hasNext(); ) + { + eventBus.unregister( it.next() ); + it.remove(); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/TabExecutor.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/TabExecutor.java new file mode 100644 index 0000000..cdf5174 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/plugin/TabExecutor.java @@ -0,0 +1,10 @@ +package net.md_5.bungee.api.plugin; + +import net.md_5.bungee.api.CommandSender; + + +public interface TabExecutor +{ + + public Iterable onTabComplete(CommandSender sender, String[] args); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/scheduler/ScheduledTask.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/scheduler/ScheduledTask.java new file mode 100644 index 0000000..bc40b88 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/scheduler/ScheduledTask.java @@ -0,0 +1,36 @@ +package net.md_5.bungee.api.scheduler; + +import net.md_5.bungee.api.plugin.Plugin; + +/** + * Represents a task scheduled for execution by the {@link TaskScheduler}. + */ +public interface ScheduledTask +{ + + /** + * Gets the unique ID of this task. + * + * @return this tasks ID + */ + int getId(); + + /** + * Return the plugin which scheduled this task for execution. + * + * @return the owning plugin + */ + Plugin getOwner(); + + /** + * Get the actual method which will be executed by this task. + * + * @return the {@link Runnable} behind this task + */ + Runnable getTask(); + + /** + * Cancel this task to suppress subsequent executions. + */ + void cancel(); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/scheduler/TaskScheduler.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/scheduler/TaskScheduler.java new file mode 100644 index 0000000..a5062eb --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/scheduler/TaskScheduler.java @@ -0,0 +1,74 @@ +package net.md_5.bungee.api.scheduler; + +import java.util.concurrent.TimeUnit; +import net.md_5.bungee.api.plugin.Plugin; + +/** + * This interface represents a scheduler which may be used to queue, delay and + * execute tasks in an asynchronous fashion. + */ +public interface TaskScheduler +{ + + /** + * Cancel a task to prevent it from executing, or if its a repeating task, + * prevent its further execution. + * + * @param id the id of the task to cancel + */ + void cancel(int id); + + /** + * Cancel a task to prevent it from executing, or if its a repeating task, + * prevent its further execution. + * + * @param task the task to cancel + */ + void cancel(ScheduledTask task); + + /** + * Cancel all tasks owned by this plugin, this preventing them from being + * executed hereon in. + * + * @param plugin the plugin owning the tasks to be cancelled + * @return the number of tasks cancelled by this method + */ + int cancel(Plugin plugin); + + /** + * Schedule a task to be executed asynchronously. The task will commence + * running as soon as this method returns. + * + * @param owner the plugin owning this task + * @param task the task to run + * @return the scheduled task + */ + ScheduledTask runAsync(Plugin owner, Runnable task); + + /** + * Schedules a task to be executed asynchronously after the specified delay + * is up. + * + * @param owner the plugin owning this task + * @param task the task to run + * @param delay the delay before this task will be executed + * @param unit the unit in which the delay will be measured + * @return the scheduled task + */ + ScheduledTask schedule(Plugin owner, Runnable task, long delay, TimeUnit unit); + + /** + * Schedules a task to be executed asynchronously after the specified delay + * is up. The scheduled task will continue running at the specified + * interval. The interval will not begin to count down until the last task + * invocation is complete. + * + * @param owner the plugin owning this task + * @param task the task to run + * @param delay the delay in milliseconds before this task will be executed + * @param period the interval before subsequent executions of this task + * @param unit the unit in which the delay and period will be measured + * @return the scheduled task + */ + ScheduledTask schedule(Plugin owner, Runnable task, long delay, long period, TimeUnit unit); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Objective.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Objective.java new file mode 100644 index 0000000..ec1d0e1 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Objective.java @@ -0,0 +1,89 @@ +package net.md_5.bungee.api.score; + +import java.beans.ConstructorProperties; + +/** + * Represents an objective entry. + */ +public class Objective +{ + + /** + * Name of the objective. + */ + private final String name; + /** + * Value of the objective. + */ + private final String value; // displayName + + @ConstructorProperties({ "name", "value" }) + public Objective(final String name, final String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return this.name; + } + + public String getValue() { + return this.value; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Objective)) { + return false; + } + final Objective other = (Objective) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$name = this.getName(); + final Object other$name = other.getName(); + Label_0065: { + if (this$name == null) { + if (other$name == null) { + break Label_0065; + } + } else if (this$name.equals(other$name)) { + break Label_0065; + } + return false; + } + final Object this$value = this.getValue(); + final Object other$value = other.getValue(); + if (this$value == null) { + if (other$value == null) { + return true; + } + } else if (this$value.equals(other$value)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof Objective; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * 31 + (($name == null) ? 0 : $name.hashCode()); + final Object $value = this.getValue(); + result = result * 31 + (($value == null) ? 0 : $value.hashCode()); + return result; + } + + @Override + public String toString() { + return "Objective(name=" + this.getName() + ", value=" + this.getValue() + ")"; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Position.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Position.java new file mode 100644 index 0000000..e68d066 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Position.java @@ -0,0 +1,10 @@ +package net.md_5.bungee.api.score; + +/** + * Represents locations for a scoreboard to be displayed. + */ +public enum Position +{ + + LIST, SIDEBAR, BELOW; +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Score.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Score.java new file mode 100644 index 0000000..4384c33 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Score.java @@ -0,0 +1,99 @@ +package net.md_5.bungee.api.score; + +import java.beans.ConstructorProperties; + +/** + * Represents a scoreboard score entry. + */ +public class Score +{ + + /** + * Name to be displayed in the list. + */ + private final String itemName; // Player + /** + * Unique name of the score. + */ + private final String scoreName; // Score + /** + * Value of the score. + */ + private final int value; + + @ConstructorProperties({ "itemName", "scoreName", "value" }) + public Score(final String itemName, final String scoreName, final int value) { + this.itemName = itemName; + this.scoreName = scoreName; + this.value = value; + } + + public String getItemName() { + return this.itemName; + } + + public String getScoreName() { + return this.scoreName; + } + + public int getValue() { + return this.value; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Score)) { + return false; + } + final Score other = (Score) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$itemName = this.getItemName(); + final Object other$itemName = other.getItemName(); + Label_0065: { + if (this$itemName == null) { + if (other$itemName == null) { + break Label_0065; + } + } else if (this$itemName.equals(other$itemName)) { + break Label_0065; + } + return false; + } + final Object this$scoreName = this.getScoreName(); + final Object other$scoreName = other.getScoreName(); + if (this$scoreName == null) { + if (other$scoreName == null) { + return this.getValue() == other.getValue(); + } + } else if (this$scoreName.equals(other$scoreName)) { + return this.getValue() == other.getValue(); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof Score; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $itemName = this.getItemName(); + result = result * 31 + (($itemName == null) ? 0 : $itemName.hashCode()); + final Object $scoreName = this.getScoreName(); + result = result * 31 + (($scoreName == null) ? 0 : $scoreName.hashCode()); + result = result * 31 + this.getValue(); + return result; + } + + @Override + public String toString() { + return "Score(itemName=" + this.getItemName() + ", scoreName=" + this.getScoreName() + ", value=" + this.getValue() + ")"; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Scoreboard.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Scoreboard.java new file mode 100644 index 0000000..93791d1 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Scoreboard.java @@ -0,0 +1,193 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.score; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.google.common.base.Preconditions; + +public class Scoreboard { + private String name; + private Position position; + private final Map objectives; + private final Map scores; + private final Map teams; + + public Collection getObjectives() { + return Collections.unmodifiableCollection((Collection) this.objectives.values()); + } + + public Collection getScores() { + return Collections.unmodifiableCollection((Collection) this.scores.values()); + } + + public Collection getTeams() { + return Collections.unmodifiableCollection((Collection) this.teams.values()); + } + + public void addObjective(final Objective objective) { + Preconditions.checkNotNull((Object) objective, (Object) "objective"); + Preconditions.checkArgument(!this.objectives.containsKey(objective.getName()), "Objective %s already exists in this scoreboard", new Object[] { objective.getName() }); + this.objectives.put(objective.getName(), objective); + } + + public void addScore(final Score score) { + Preconditions.checkNotNull((Object) score, (Object) "score"); + this.scores.put(score.getItemName(), score); + } + + public void addTeam(final Team team) { + Preconditions.checkNotNull((Object) team, (Object) "team"); + Preconditions.checkArgument(!this.teams.containsKey(team.getName()), "Team %s already exists in this scoreboard", new Object[] { team.getName() }); + this.teams.put(team.getName(), team); + } + + public Team getTeam(final String name) { + return this.teams.get(name); + } + + public void removeObjective(final String objectiveName) { + this.objectives.remove(objectiveName); + } + + public void removeScore(final String scoreName) { + this.scores.remove(scoreName); + } + + public void removeTeam(final String teamName) { + this.teams.remove(teamName); + } + + public void clear() { + this.name = null; + this.position = null; + this.objectives.clear(); + this.scores.clear(); + this.teams.clear(); + } + + public String getName() { + return this.name; + } + + public Position getPosition() { + return this.position; + } + + public void setName(final String name) { + this.name = name; + } + + public void setPosition(final Position position) { + this.position = position; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Scoreboard)) { + return false; + } + final Scoreboard other = (Scoreboard) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$name = this.getName(); + final Object other$name = other.getName(); + Label_0065: { + if (this$name == null) { + if (other$name == null) { + break Label_0065; + } + } else if (this$name.equals(other$name)) { + break Label_0065; + } + return false; + } + final Object this$position = this.getPosition(); + final Object other$position = other.getPosition(); + Label_0102: { + if (this$position == null) { + if (other$position == null) { + break Label_0102; + } + } else if (this$position.equals(other$position)) { + break Label_0102; + } + return false; + } + final Object this$objectives = this.getObjectives(); + final Object other$objectives = other.getObjectives(); + Label_0139: { + if (this$objectives == null) { + if (other$objectives == null) { + break Label_0139; + } + } else if (this$objectives.equals(other$objectives)) { + break Label_0139; + } + return false; + } + final Object this$scores = this.getScores(); + final Object other$scores = other.getScores(); + Label_0176: { + if (this$scores == null) { + if (other$scores == null) { + break Label_0176; + } + } else if (this$scores.equals(other$scores)) { + break Label_0176; + } + return false; + } + final Object this$teams = this.getTeams(); + final Object other$teams = other.getTeams(); + if (this$teams == null) { + if (other$teams == null) { + return true; + } + } else if (this$teams.equals(other$teams)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof Scoreboard; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * 31 + (($name == null) ? 0 : $name.hashCode()); + final Object $position = this.getPosition(); + result = result * 31 + (($position == null) ? 0 : $position.hashCode()); + final Object $objectives = this.getObjectives(); + result = result * 31 + (($objectives == null) ? 0 : $objectives.hashCode()); + final Object $scores = this.getScores(); + result = result * 31 + (($scores == null) ? 0 : $scores.hashCode()); + final Object $teams = this.getTeams(); + result = result * 31 + (($teams == null) ? 0 : $teams.hashCode()); + return result; + } + + @Override + public String toString() { + return "Scoreboard(name=" + this.getName() + ", position=" + this.getPosition() + ", objectives=" + this.getObjectives() + ", scores=" + this.getScores() + ", teams=" + this.getTeams() + ")"; + } + + public Scoreboard() { + this.objectives = new HashMap(); + this.scores = new HashMap(); + this.teams = new HashMap(); + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Team.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Team.java new file mode 100644 index 0000000..5072b0e --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/score/Team.java @@ -0,0 +1,183 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.score; + +import java.beans.ConstructorProperties; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class Team { + private final String name; + private String displayName; + private String prefix; + private String suffix; + private boolean friendlyFire; + private Set players; + + public Collection getPlayers() { + return (Collection) (Collection) Collections.unmodifiableSet((Set) this.players); + } + + public void addPlayer(final String name) { + this.players.add(name); + } + + public void removePlayer(final String name) { + this.players.remove(name); + } + + @ConstructorProperties({ "name" }) + public Team(final String name) { + this.players = new HashSet(); + if (name == null) { + throw new NullPointerException("name"); + } + this.name = name; + } + + public String getName() { + return this.name; + } + + public String getDisplayName() { + return this.displayName; + } + + public String getPrefix() { + return this.prefix; + } + + public String getSuffix() { + return this.suffix; + } + + public boolean isFriendlyFire() { + return this.friendlyFire; + } + + public void setDisplayName(final String displayName) { + this.displayName = displayName; + } + + public void setPrefix(final String prefix) { + this.prefix = prefix; + } + + public void setSuffix(final String suffix) { + this.suffix = suffix; + } + + public void setFriendlyFire(final boolean friendlyFire) { + this.friendlyFire = friendlyFire; + } + + public void setPlayers(final Set players) { + this.players = players; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Team)) { + return false; + } + final Team other = (Team) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$name = this.getName(); + final Object other$name = other.getName(); + Label_0065: { + if (this$name == null) { + if (other$name == null) { + break Label_0065; + } + } else if (this$name.equals(other$name)) { + break Label_0065; + } + return false; + } + final Object this$displayName = this.getDisplayName(); + final Object other$displayName = other.getDisplayName(); + Label_0102: { + if (this$displayName == null) { + if (other$displayName == null) { + break Label_0102; + } + } else if (this$displayName.equals(other$displayName)) { + break Label_0102; + } + return false; + } + final Object this$prefix = this.getPrefix(); + final Object other$prefix = other.getPrefix(); + Label_0139: { + if (this$prefix == null) { + if (other$prefix == null) { + break Label_0139; + } + } else if (this$prefix.equals(other$prefix)) { + break Label_0139; + } + return false; + } + final Object this$suffix = this.getSuffix(); + final Object other$suffix = other.getSuffix(); + Label_0176: { + if (this$suffix == null) { + if (other$suffix == null) { + break Label_0176; + } + } else if (this$suffix.equals(other$suffix)) { + break Label_0176; + } + return false; + } + if (this.isFriendlyFire() != other.isFriendlyFire()) { + return false; + } + final Object this$players = this.getPlayers(); + final Object other$players = other.getPlayers(); + if (this$players == null) { + if (other$players == null) { + return true; + } + } else if (this$players.equals(other$players)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof Team; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * 31 + (($name == null) ? 0 : $name.hashCode()); + final Object $displayName = this.getDisplayName(); + result = result * 31 + (($displayName == null) ? 0 : $displayName.hashCode()); + final Object $prefix = this.getPrefix(); + result = result * 31 + (($prefix == null) ? 0 : $prefix.hashCode()); + final Object $suffix = this.getSuffix(); + result = result * 31 + (($suffix == null) ? 0 : $suffix.hashCode()); + result = result * 31 + (this.isFriendlyFire() ? 1231 : 1237); + final Object $players = this.getPlayers(); + result = result * 31 + (($players == null) ? 0 : $players.hashCode()); + return result; + } + + @Override + public String toString() { + return "Team(name=" + this.getName() + ", displayName=" + this.getDisplayName() + ", prefix=" + this.getPrefix() + ", suffix=" + this.getSuffix() + ", friendlyFire=" + this.isFriendlyFire() + ", players=" + this.getPlayers() + ")"; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/tab/CustomTabList.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/tab/CustomTabList.java new file mode 100644 index 0000000..09aaa7f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/tab/CustomTabList.java @@ -0,0 +1,60 @@ +package net.md_5.bungee.api.tab; + +/** + * Represents a custom tab list, which may have slots manipulated. + */ +public interface CustomTabList extends TabListHandler +{ + + /** + * Blank out this tab list and update immediately. + */ + void clear(); + + /** + * Gets the columns in this list. + * + * @return the width of this list + */ + int getColumns(); + + /** + * Gets the rows in this list. + * + * @return the height of this list + */ + int getRows(); + + /** + * Get the total size of this list. + * + * @return {@link #getRows()} * {@link #getColumns()} + */ + int getSize(); + + /** + * Set the text in the specified slot and update immediately. + * + * @param row the row to set + * @param column the column to set + * @param text the text to set + * @return the padded text + */ + String setSlot(int row, int column, String text); + + /** + * Set the text in the specified slot. + * + * @param row the row to set + * @param column the column to set + * @param text the text to set + * @param update whether or not to invoke {@link #update()} upon completion + * @return the padded text + */ + String setSlot(int row, int column, String text, boolean update); + + /** + * Flush all queued changes to the user. + */ + void update(); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/tab/TabListAdapter.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/tab/TabListAdapter.java new file mode 100644 index 0000000..c344010 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/tab/TabListAdapter.java @@ -0,0 +1,36 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.api.tab; + +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public abstract class TabListAdapter implements TabListHandler { + private ProxiedPlayer player; + + @Override + public void init(final ProxiedPlayer player) { + this.player = player; + } + + @Override + public void onConnect() { + } + + @Override + public void onDisconnect() { + } + + @Override + public void onServerChange() { + } + + @Override + public void onPingChange(final int ping) { + } + + public ProxiedPlayer getPlayer() { + return this.player; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/api/tab/TabListHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/api/tab/TabListHandler.java new file mode 100644 index 0000000..e47d4ad --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/api/tab/TabListHandler.java @@ -0,0 +1,55 @@ +package net.md_5.bungee.api.tab; + +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public interface TabListHandler +{ + + /** + * Called so that this class may set member fields to keep track of its + * internal state. You should not do any packet sending or manipulation of + * the passed player, other than storing it. + * + * @param player the player to be associated with this list + */ + void init(ProxiedPlayer player); + + /** + * Called when this player first connects to the proxy. + */ + void onConnect(); + + /** + * Called when a player first connects to the proxy. + * + * @param player the connecting player + */ + void onServerChange(); + + /** + * Called when a players ping changes. The new ping will have not updated in + * the player instance until this method returns. + * + * @param player the player who's ping changed + * @param ping the player's new ping. + */ + void onPingChange(int ping); + + /** + * Called when a player disconnects. + * + * @param player the disconnected player + */ + void onDisconnect(); + + /** + * Called when a list update packet is sent from server to client. + * + * @param player receiving this packet + * @param name the player which this packet is relevant to + * @param online whether the subject player is online + * @param ping ping of the subject player + * @return whether to send the packet to the client + */ + boolean onListUpdate(String name, boolean online, int ping); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandAlert.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandAlert.java new file mode 100644 index 0000000..1ccae73 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandAlert.java @@ -0,0 +1,46 @@ +package net.md_5.bungee.command; + +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.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; + +public class CommandAlert extends Command +{ + + public CommandAlert() + { + super( "alert", "bungeecord.command.alert" ); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + if ( args.length == 0 ) + { + sender.sendMessage( ChatColor.RED + "You must supply a message." ); + } else + { + StringBuilder builder = new StringBuilder(); + if ( args[0].startsWith( "&h" ) ) + { + // Remove &h + args[0] = args[0].substring( 2, args[0].length() ); + } else + { + builder.append( ProxyServer.getInstance().getTranslation( "alert" ) ); + } + + for ( String s : args ) + { + builder.append( ChatColor.translateAlternateColorCodes( '&', s ) ); + builder.append( " " ); + } + + String message = builder.substring( 0, builder.length() - 1 ); + + ProxyServer.getInstance().broadcast( message ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandBungee.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandBungee.java new file mode 100644 index 0000000..b079879 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandBungee.java @@ -0,0 +1,21 @@ +package net.md_5.bungee.command; + +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.plugin.Command; + +public class CommandBungee extends Command +{ + + public CommandBungee() + { + super( "bungee" ); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + sender.sendMessage( ChatColor.BLUE + "This server is running BungeeCord version " + ProxyServer.getInstance().getVersion() + " by md_5" ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandChangePassword.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandChangePassword.java new file mode 100644 index 0000000..1a29b2d --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandChangePassword.java @@ -0,0 +1,35 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.AuthSystem; + +public class CommandChangePassword extends Command { + private final AuthSystem authSystem; + + public CommandChangePassword(AuthSystem authSystem) { + super("changepassword", "bungeecord.command.eag.changepassword", + new String[] { "changepwd", "changepasswd", "changepass" }); + this.authSystem = authSystem; + } + + @Override + public void execute(final CommandSender sender, final String[] args) { + if (!(sender instanceof ProxiedPlayer)) { + return; + } + String username = sender.getName(); + if (args.length == 0 || args.length == 1) { + sender.sendMessage("\u00A7cUsage: /changepassword "); + } else if (this.authSystem.login(username, args[0])) { + if (this.authSystem.changePass(username, args[1])) { + sender.sendMessage("\u00A7cPassword changed successfully!"); + } else { + sender.sendMessage("\u00A7cUnable to change your password..."); + } + } else { + sender.sendMessage("\u00A7cThe old password specified is incorrect!"); + } + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandClearRatelimit.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandClearRatelimit.java new file mode 100644 index 0000000..12440d1 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandClearRatelimit.java @@ -0,0 +1,118 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.plugin.Command; + +public class CommandClearRatelimit extends Command { + + public CommandClearRatelimit() { + super("eag-ratelimit", "bungeecord.command.eag.ratelimit", "e-ratelimit", "gratelimit"); + } + + @Override + public void execute(CommandSender p0, String[] p1) { + if(p1.length >= 1 && ("clear".equalsIgnoreCase(p1[0]) || "reset".equalsIgnoreCase(p1[0]))) { + if(p1.length == 1 || (p1.length == 2 && "all".equalsIgnoreCase(p1[1]))) { + for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) { + if(l.getRateLimitIP() != null) l.getRateLimitIP().resetLimiters(); + if(l.getRateLimitLogin() != null) l.getRateLimitLogin().resetLimiters(); + if(l.getRateLimitMOTD() != null) l.getRateLimitMOTD().resetLimiters(); + if(l.getRateLimitQuery() != null) l.getRateLimitQuery().resetLimiters(); + } + p0.sendMessage(ChatColor.GREEN + "Reset all ratelimits"); + return; + }else if(p1.length == 2 || p1.length == 3) { + ListenerInfo ll = null; + if(p1.length == 3) { + for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) { + if(l.getHostString().equalsIgnoreCase(p1[2])) { + ll = l; + break; + } + } + if(ll == null) { + p0.sendMessage(ChatColor.RED + "Listener does not exist: " + ChatColor.WHITE + p1[2]); + String accum = ""; + for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) { + if(accum.length() > 0) { + accum += ", "; + } + accum += l.getHostString(); + } + p0.sendMessage(ChatColor.GREEN + "Listeners Available: " + ChatColor.WHITE + accum); + return; + } + } + if("all".equalsIgnoreCase(p1[1])) { + if(ll != null) { + if(ll.getRateLimitIP() != null) ll.getRateLimitIP().resetLimiters(); + if(ll.getRateLimitLogin() != null) ll.getRateLimitLogin().resetLimiters(); + if(ll.getRateLimitMOTD() != null) ll.getRateLimitMOTD().resetLimiters(); + if(ll.getRateLimitQuery() != null) ll.getRateLimitQuery().resetLimiters(); + p0.sendMessage(ChatColor.GREEN + "Reset all ratelimits on listener: " + ChatColor.WHITE + ll.getHostString()); + }else { + for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) { + if(l.getRateLimitIP() != null) l.getRateLimitIP().resetLimiters(); + if(l.getRateLimitLogin() != null) l.getRateLimitLogin().resetLimiters(); + if(l.getRateLimitMOTD() != null) l.getRateLimitMOTD().resetLimiters(); + if(l.getRateLimitQuery() != null) l.getRateLimitQuery().resetLimiters(); + } + p0.sendMessage(ChatColor.GREEN + "Reset all ratelimits"); + } + return; + }else if("ip".equalsIgnoreCase(p1[1])) { + if(ll != null) { + if(ll.getRateLimitIP() != null) ll.getRateLimitIP().resetLimiters(); + p0.sendMessage(ChatColor.GREEN + "Reset all IP ratelimits on listener: " + ChatColor.WHITE + ll.getHostString()); + }else { + for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) { + if(l.getRateLimitIP() != null) l.getRateLimitIP().resetLimiters(); + } + p0.sendMessage(ChatColor.GREEN + "Reset all IP ratelimits."); + } + return; + }else if("login".equalsIgnoreCase(p1[1])) { + if(ll != null) { + if(ll.getRateLimitLogin() != null) ll.getRateLimitLogin().resetLimiters(); + p0.sendMessage(ChatColor.GREEN + "Reset all login ratelimits on listener: " + ChatColor.WHITE + ll.getHostString()); + }else { + for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) { + if(l.getRateLimitLogin() != null) l.getRateLimitLogin().resetLimiters(); + } + p0.sendMessage(ChatColor.GREEN + "Reset all login ratelimits."); + } + return; + }else if("motd".equalsIgnoreCase(p1[1])) { + if(ll != null) { + if(ll.getRateLimitMOTD() != null) ll.getRateLimitMOTD().resetLimiters(); + p0.sendMessage(ChatColor.GREEN + "Reset all MOTD ratelimits on listener: " + ChatColor.WHITE + ll.getHostString()); + }else { + for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) { + if(l.getRateLimitMOTD() != null) l.getRateLimitMOTD().resetLimiters(); + } + p0.sendMessage(ChatColor.GREEN + "Reset all MOTD ratelimits."); + } + return; + }else if("query".equalsIgnoreCase(p1[1])) { + if(ll != null) { + if(ll.getRateLimitMOTD() != null) ll.getRateLimitMOTD().resetLimiters(); + p0.sendMessage(ChatColor.GREEN + "Reset all query ratelimits on listener: " + ChatColor.WHITE + ll.getHostString()); + }else { + for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) { + if(l.getRateLimitMOTD() != null) l.getRateLimitMOTD().resetLimiters(); + } + p0.sendMessage(ChatColor.GREEN + "Reset all query ratelimits."); + } + return; + } + } + } + p0.sendMessage(ChatColor.RED + "How to reset all rate limits: " + ChatColor.WHITE + "/eag-ratelimit reset"); + p0.sendMessage(ChatColor.RED + "How to reset a specific rate limit: " + ChatColor.WHITE + "/eag-ratelimit reset "); + p0.sendMessage(ChatColor.RED + "How to reset a specific listener: " + ChatColor.WHITE + "/eag-ratelimit reset "); + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandConfirmCode.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandConfirmCode.java new file mode 100644 index 0000000..d5bc09d --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandConfirmCode.java @@ -0,0 +1,33 @@ +package net.md_5.bungee.command; + +import java.nio.charset.StandardCharsets; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.QueryConnectionImpl; +import net.md_5.bungee.eaglercraft.SHA1Digest; + +public class CommandConfirmCode extends Command { + + public CommandConfirmCode() { + super("confirm-code", "bungeecord.command.eag.confirmcode", "confirmcode"); + } + + @Override + public void execute(CommandSender p0, String[] p1) { + if(p1.length != 1) { + p0.sendMessage(ChatColor.RED + "How to use: " + ChatColor.WHITE + "/confirm-code "); + }else { + p0.sendMessage(ChatColor.YELLOW + "Server list 2FA code has been set to: " + ChatColor.GREEN + p1[0]); + p0.sendMessage(ChatColor.YELLOW + "You can now return to the server list site and continue"); + byte[] bts = p1[0].getBytes(StandardCharsets.US_ASCII); + SHA1Digest dg = new SHA1Digest(); + dg.update(bts, 0, bts.length); + byte[] f = new byte[20]; + dg.doFinal(f, 0); + QueryConnectionImpl.confirmHash = SHA1Digest.hash2string(f); + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomain.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomain.java new file mode 100644 index 0000000..fbb985b --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomain.java @@ -0,0 +1,37 @@ +package net.md_5.bungee.command; + +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.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; + +public class CommandDomain extends Command { + + public CommandDomain() { + super("domain", "bungeecord.command.eag.domain"); + } + + @Override + public void execute(CommandSender p0, String[] p1) { + if (p1.length < 1) { + p0.sendMessage(ChatColor.RED + "Please follow this command by a user name"); + return; + } + final ProxiedPlayer user = ProxyServer.getInstance().getPlayer(p1[0]); + if (user == null) { + p0.sendMessage(ChatColor.RED + "That user is not online"); + } else { + Object o = user.getAttachment().get("origin"); + if(o != null) { + p0.sendMessage(ChatColor.BLUE + "Domain of " + p1[0] + " is " + o); + if(p0.hasPermission("bungeecord.command.eag.blockdomain")) { + p0.sendMessage(ChatColor.BLUE + "Type " + ChatColor.WHITE + "/block-domain " + p1[0] + ChatColor.BLUE + " to block this person"); + } + }else { + p0.sendMessage(ChatColor.RED + "Domain of " + p1[0] + " is unknown"); + } + } + } + +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomainBlock.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomainBlock.java new file mode 100644 index 0000000..983b8e6 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomainBlock.java @@ -0,0 +1,38 @@ +package net.md_5.bungee.command; + +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.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.DomainBlacklist; + +public class CommandDomainBlock extends Command { + + public CommandDomainBlock() { + super("block-domain", "bungeecord.command.eag.blockdomain"); + } + + @Override + public void execute(CommandSender p0, String[] p1) { + if (p1.length < 1) { + p0.sendMessage(ChatColor.RED + "Please follow this command by a username"); + return; + } + final ProxiedPlayer user = ProxyServer.getInstance().getPlayer(p1[0]); + if (user == null) { + p0.sendMessage(ChatColor.RED + "That user is not online"); + }else { + Object o = user.getAttachment().get("origin"); + if(o != null) { + DomainBlacklist.addLocal((String)o); + p0.sendMessage(ChatColor.RED + "Domain of " + ChatColor.WHITE + p1[0] + ChatColor.RED + " is " + ChatColor.WHITE + o); + p0.sendMessage(ChatColor.RED + "It was added to the local block list."); + user.disconnect("client blocked"); + }else { + p0.sendMessage(ChatColor.RED + "Domain of " + p1[0] + " is unknown"); + } + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomainBlockDomain.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomainBlockDomain.java new file mode 100644 index 0000000..8533de8 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomainBlockDomain.java @@ -0,0 +1,24 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.DomainBlacklist; + +public class CommandDomainBlockDomain extends Command { + + public CommandDomainBlockDomain() { + super("block-domain-name", "bungeecord.command.eag.blockdomainname"); + } + + @Override + public void execute(CommandSender p0, String[] p1) { + if (p1.length < 1) { + p0.sendMessage(ChatColor.RED + "Please follow this command by a domain"); + return; + } + DomainBlacklist.addLocal(p1[0]); + p0.sendMessage(ChatColor.GREEN + "The domain '" + ChatColor.WHITE + p1[0] + ChatColor.GREEN + "' was added to the block list"); + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomainUnblock.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomainUnblock.java new file mode 100644 index 0000000..2a0ab1b --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandDomainUnblock.java @@ -0,0 +1,27 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.DomainBlacklist; + +public class CommandDomainUnblock extends Command { + + public CommandDomainUnblock() { + super("unblock-domain", "bungeecord.command.eag.unblockdomain", "unblock-domain-name"); + } + + @Override + public void execute(CommandSender p0, String[] p1) { + if (p1.length < 1) { + p0.sendMessage(ChatColor.RED + "Please follow this command by a domain"); + return; + } + if(DomainBlacklist.removeLocal(p1[0])) { + p0.sendMessage(ChatColor.GREEN + "The domain '" + p1[0] + "' was removed from the local block list"); + }else { + p0.sendMessage(ChatColor.RED + "The domain was not removed, is it on the block list? Check '" + DomainBlacklist.localBlacklist.getName() + "' in your bungeecord directory"); + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandEnd.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandEnd.java new file mode 100644 index 0000000..e8fd283 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandEnd.java @@ -0,0 +1,23 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; + +/** + * Command to terminate the proxy instance. May only be used by the console. + */ +public class CommandEnd extends Command +{ + + public CommandEnd() + { + super( "end", "bungeecord.command.end" ); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + BungeeCord.getInstance().stop(); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandFind.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandFind.java new file mode 100644 index 0000000..10e99d7 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandFind.java @@ -0,0 +1,35 @@ +package net.md_5.bungee.command; + +import java.util.Collections; +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.connection.ProxiedPlayer; + +public class CommandFind extends PlayerCommand +{ + + public CommandFind() + { + super( "find", "bungeecord.command.find" ); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + if ( args.length != 1 ) + { + sender.sendMessage( ChatColor.RED + "Please follow this command by a user name" ); + } else + { + ProxiedPlayer player = ProxyServer.getInstance().getPlayer( args[0] ); + if ( player == null || player.getServer() == null ) + { + sender.sendMessage( ChatColor.RED + "That user is not online" ); + } else + { + sender.sendMessage( ChatColor.BLUE + args[0] + " is online at " + player.getServer().getInfo().getName() ); + } + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBan.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBan.java new file mode 100644 index 0000000..2ee3dab --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBan.java @@ -0,0 +1,61 @@ +package net.md_5.bungee.command; + +import java.util.Collection; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.BanList; + +public class CommandGlobalBan extends Command { + + private final boolean replaceBukkit; + + public CommandGlobalBan(boolean replaceBukkit) { + super(replaceBukkit ? "ban" : "eag-ban", "bungeecord.command.eag.ban", replaceBukkit ? new String[] { "kickban", "eag-ban", "e-ban", "gban" } : new String[] { "e-ban", "gban" }); + this.replaceBukkit = replaceBukkit; + } + + @Override + public void execute(CommandSender p0, String[] p1) { + if(p1.length >= 1) { + String p = p1[0].trim().toLowerCase(); + if(p0.getName().equalsIgnoreCase(p)) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "You cannot ban yourself"); + return; + } + String reason = "The ban hammer has spoken!"; + if(p1.length >= 2) { + reason = ""; + for(int i = 1; i < p1.length; ++i) { + if(reason.length() > 0) { + reason += " "; + } + reason += p1[i]; + } + } + String wasTheKick = null; + Collection playerz = BungeeCord.getInstance().getPlayers(); + for(ProxiedPlayer pp : playerz) { + if(pp.getName().equalsIgnoreCase(p)) { + wasTheKick = pp.getName(); + pp.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: " + reason); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.WHITE + "Kicked: " + pp.getName()); + } + } + if(BanList.ban(p, reason)) { + if(wasTheKick == null) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Warning! '" + ChatColor.WHITE + p + ChatColor.YELLOW + "' is not currently on this server"); + } + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Username '" + ChatColor.WHITE + (wasTheKick == null ? p : wasTheKick) + ChatColor.GREEN + "' was added to the ban list"); + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Username '" + ChatColor.WHITE + p + ChatColor.RED + "' is already banned"); + } + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To ban a player, use: " + ChatColor.WHITE + "/" + (replaceBukkit?"":"eag-") + "ban [reason]"); + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanIP.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanIP.java new file mode 100644 index 0000000..3f2d323 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanIP.java @@ -0,0 +1,147 @@ +package net.md_5.bungee.command; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.BanList; +import net.md_5.bungee.eaglercraft.BanList.IPBan; + +public class CommandGlobalBanIP extends Command { + + private final boolean replaceBukkit; + + public CommandGlobalBanIP(boolean replaceBukkit) { + super(replaceBukkit ? "ban-ip" : "eag-ban-ip", "bungeecord.command.eag.banip", (replaceBukkit ? new String[] {"eag-ban-ip", "banip", "e-ban-ip", "gban-ip"} : + new String[] {"gban-ip", "e-ban-ip", "gbanip", "e-banip"}) ); + this.replaceBukkit = replaceBukkit; + } + + @Override + public void execute(CommandSender p0, String[] p1) { + String w = (String) p0.getAttachment().get("banIPWaitingToAdd"); + if(w != null) { + List lst = (List)p0.getAttachment().get("banIPWaitingToKick"); + if(p1.length != 1 || (!p1[0].equalsIgnoreCase("confirm") && !p1[0].equalsIgnoreCase("cancel"))) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-ip" : "/eag-ban-ip") + " confirm" + ChatColor.RED + " to add IP " + ChatColor.WHITE + w + + ChatColor.RED + " and ban " + ChatColor.WHITE + lst.size() + ChatColor.RED + " players"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-ip" : "/eag-ban-ip") + " cancel" + ChatColor.RED + " to cancel this operation"); + }else { + if(p1[0].equalsIgnoreCase("confirm")) { + try { + if(BanList.banIP(w)) { + for(ProxiedPlayer pp : lst) { + pp.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by IP"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + pp.getName()); + } + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added IP '" + ChatColor.WHITE + w + ChatColor.GREEN + "' to the ban list"); + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "IP '" + ChatColor.WHITE + w + ChatColor.RED + "' is already on the ban list"); + } + } catch (UnknownHostException e) { + e.printStackTrace(); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "ERROR: address '" + ChatColor.WHITE + w + ChatColor.RED + "' is suddenly invalid for some reason"); + } + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Canceled ban"); + } + p0.getAttachment().remove("banIPWaitingToAdd"); + p0.getAttachment().remove("banIPWaitingToKick"); + } + return; + } + if(p1.length != 1) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "How to use: " + ChatColor.WHITE + (replaceBukkit ? "/ban-ip" : "/eag-ban-ip") + " "); + return; + } + boolean isPlayer = false; + IPBan p = null; + try { + p = BanList.constructIpBan(p1[0]); + }catch(Throwable t) { + for(ProxiedPlayer pp : BungeeCord.getInstance().getPlayers()) { + if(pp.getName().equalsIgnoreCase(p1[0])) { + Object addr = pp.getAttachment().get("remoteAddr"); + if(addr != null) { + String newAddr = ((InetAddress)addr).getHostAddress(); + isPlayer = true; + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Player '" + ChatColor.WHITE + p1[0] + ChatColor.GREEN + "' has IP " + ChatColor.WHITE + newAddr); + p1[0] = newAddr; + try { + p = BanList.constructIpBan(p1[0]); + }catch(UnknownHostException ex) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Address '" + ChatColor.WHITE + p1[0] + "' is suddenly invalid: " + ChatColor.WHITE + p1[0]); + return; + } + } + break; + } + } + if(!isPlayer) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Player '" + ChatColor.WHITE + p1[0] + "' is not on this server"); + return; + } + } + boolean blocked = false; + for(IPBan b : BanList.blockedBans) { + if(b.checkBan(p.getBaseAddress()) || p.checkBan(b.getBaseAddress())) { + blocked = true; + } + } + if(blocked) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Cannot ban '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "', it will ban local addresses that may break your game"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To force, add to the " + ChatColor.WHITE + "[IPs]" + ChatColor.RED + " section of " + ChatColor.WHITE + "bans.txt" + ChatColor.RED + " in your bungee directory"); + return; + } + boolean isSenderGonnaGetKicked = false; + List usersThatAreGonnaBeKicked = new ArrayList(); + for(ProxiedPlayer pp : BungeeCord.getInstance().getPlayers()) { + Object addr = pp.getAttachment().get("remoteAddr"); + if(addr != null) { + InetAddress addrr = (InetAddress)addr; + if(p.checkBan(addrr)) { + usersThatAreGonnaBeKicked.add(pp); + if(pp.getName().equalsIgnoreCase(p0.getName())) { + isSenderGonnaGetKicked = true; + break; + } + } + } + } + if(isSenderGonnaGetKicked) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "banning address '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' will ban you off of your own server"); + return; + } + if(usersThatAreGonnaBeKicked.size() > 1) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "WARNING: banning address '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is gonna ban " + + ChatColor.WHITE + usersThatAreGonnaBeKicked.size() + ChatColor.RED + " players off of your server"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-ip" : "/eag-ban-ip") + " confirm" + ChatColor.RED + " to continue, or type " + + ChatColor.WHITE + (replaceBukkit ? "/ban-ip" : "/eag-ban-ip") + " cancel" + ChatColor.RED + " to cancel"); + p0.getAttachment().put("banIPWaitingToKick", usersThatAreGonnaBeKicked); + p0.getAttachment().put("banIPWaitingToAdd", p1[0]); + }else { + try { + if(BanList.banIP(p1[0])) { + if(usersThatAreGonnaBeKicked.size() > 0) { + usersThatAreGonnaBeKicked.get(0).disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by IP"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + usersThatAreGonnaBeKicked.get(0).getName()); + } + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added IP '" + ChatColor.WHITE + p1[0] + ChatColor.GREEN + "' to the ban list"); + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "IP '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is already on the ban list"); + } + } catch (UnknownHostException e) { + e.printStackTrace(); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "ERROR: address '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is suddenly invalid for some reason"); + return; + } + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanRegex.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanRegex.java new file mode 100644 index 0000000..076711f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanRegex.java @@ -0,0 +1,106 @@ +package net.md_5.bungee.command; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.BanList; + +public class CommandGlobalBanRegex extends Command { + + private final boolean replaceBukkit; + + public CommandGlobalBanRegex(boolean replaceBukkit) { + super(replaceBukkit ? "ban-regex" : "eag-ban-regex", "bungeecord.command.eag.banregex", replaceBukkit ? new String[] { "eag-ban-regex", "e-ban-regex", + "gban-regex", "eag-banregex", "e-banregex", "gbanregex", "banregex" } : new String[] { "e-ban-regex", "gban-regex", + "eag-banregex", "e-banregex", "gbanregex" }); + this.replaceBukkit = replaceBukkit; + } + + @Override + public void execute(CommandSender p0, String[] p1) { + String w = (String) p0.getAttachment().get("banRegexWaitingToAdd"); + if(w != null) { + List lst = (List)p0.getAttachment().get("banRegexWaitingToKick"); + if(p1.length != 1 || (!p1[0].equalsIgnoreCase("confirm") && !p1[0].equalsIgnoreCase("cancel"))) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-regex" : "/eag-ban-regex") + " confirm" + ChatColor.RED + " to add regex " + ChatColor.WHITE + w + + ChatColor.RED + " and ban " + ChatColor.WHITE + lst.size() + ChatColor.RED + " players"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-regex" : "/eag-ban-regex") + " cancel" + ChatColor.RED + " to cancel this operation"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched"); + }else { + if(p1[0].equalsIgnoreCase("confirm")) { + if(BanList.banRegex(w)) { + for(ProxiedPlayer pp : lst) { + pp.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by regex"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + pp.getName()); + } + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added regex '" + ChatColor.WHITE + w + ChatColor.GREEN + "' to the ban list"); + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Regex '" + ChatColor.WHITE + w + ChatColor.RED + "' is already banned"); + } + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Canceled ban"); + } + p0.getAttachment().remove("banRegexWaitingToAdd"); + p0.getAttachment().remove("banRegexWaitingToKick"); + } + return; + } + if(p1.length != 1) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "How to use: " + ChatColor.WHITE + (replaceBukkit ? "/ban-regex" : "/eag-ban-regex") + " "); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched"); + return; + } + Pattern p; + try { + p = Pattern.compile(p1[0]); + }catch(Throwable t) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Regex syntax error: " + t.getMessage()); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched"); + return; + } + boolean isSenderGonnaGetKicked = false; + List usersThatAreGonnaBeKicked = new ArrayList(); + for(ProxiedPlayer pp : BungeeCord.getInstance().getPlayers()) { + String n = pp.getName().toLowerCase(); + if(p.matcher(n).matches()) { + usersThatAreGonnaBeKicked.add(pp); + if(n.equalsIgnoreCase(p0.getName())) { + isSenderGonnaGetKicked = true; + break; + } + } + } + if(isSenderGonnaGetKicked) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "banning regex '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is gonna ban your own username"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched"); + return; + } + if(usersThatAreGonnaBeKicked.size() > 1) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "WARNING: banning regex '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is gonna ban " + + ChatColor.WHITE + usersThatAreGonnaBeKicked.size() + ChatColor.RED + " players off of your server"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-regex" : "/eag-ban-regex") + " confirm" + ChatColor.RED + " to continue, or type " + + ChatColor.WHITE + "/eag-ban-regex cancel" + ChatColor.RED + " to cancel"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched"); + p0.getAttachment().put("banRegexWaitingToKick", usersThatAreGonnaBeKicked); + p0.getAttachment().put("banRegexWaitingToAdd", p1[0]); + }else { + if(BanList.banRegex(p1[0])) { + if(usersThatAreGonnaBeKicked.size() > 0) { + usersThatAreGonnaBeKicked.get(0).disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by regex"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + usersThatAreGonnaBeKicked.get(0).getName()); + } + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added regex '" + ChatColor.WHITE + p1[0] + ChatColor.GREEN + "' to the ban list"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched"); + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Regex '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is already banned"); + } + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanReload.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanReload.java new file mode 100644 index 0000000..3063b31 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanReload.java @@ -0,0 +1,21 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.BanList; + +public class CommandGlobalBanReload extends Command { + + public CommandGlobalBanReload(boolean replaceBukkit) { + super(replaceBukkit ? "reloadban" : "eag-reloadban", "bungeecord.command.eag.reloadban", replaceBukkit ? new String[] { "eag-reloadban", "banreload", "eag-banreload", "e-reloadban", + "e-banreload", "gbanreload", "greloadban"} : new String[] { "eag-banreload", "e-reloadban", "e-banreload", "gbanreload", "greloadban"}); + } + + @Override + public void execute(CommandSender p0, String[] p1) { + BanList.maybeReloadBans(p0); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.WHITE + "Ban list reloaded"); + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanWildcard.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanWildcard.java new file mode 100644 index 0000000..1d4783a --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalBanWildcard.java @@ -0,0 +1,125 @@ +package net.md_5.bungee.command; + +import java.util.ArrayList; +import java.util.List; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.BanList; + +public class CommandGlobalBanWildcard extends Command { + + private final boolean replaceBukkit; + + public CommandGlobalBanWildcard(boolean replaceBukkit) { + super(replaceBukkit ? "ban-wildcard" : "eag-ban-wildcard", "bungeecord.command.eag.banwildcard", replaceBukkit ? new String[] { "eag-ban-wildcard", "e-ban-wildcard", "gban-wildcard", + "banwildcard", "eag-banwildcard", "banwildcard"} : new String[] { "e-ban-wildcard", "gban-wildcard", "eag-banwildcard"}); + this.replaceBukkit = replaceBukkit; + } + + @Override + public void execute(CommandSender p0, String[] p1) { + String w = (String) p0.getAttachment().get("banWildcardWaitingToAdd"); + if(w != null) { + List lst = (List)p0.getAttachment().get("banWildcardWaitingToKick"); + if(p1.length != 1 || (!p1[0].equalsIgnoreCase("confirm") && !p1[0].equalsIgnoreCase("cancel"))) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-wildcard" : "/eag-ban-wildcard") + " confirm" + ChatColor.RED + " to add wildcard " + ChatColor.WHITE + w + + ChatColor.RED + " and ban " + ChatColor.WHITE + lst.size() + ChatColor.RED + " players"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-wildcard" : "/eag-ban-wildcard") + " cancel" + ChatColor.RED + " to cancel this operation"); + }else { + if(p1[0].equalsIgnoreCase("confirm")) { + if(BanList.banWildcard(w)) { + for(ProxiedPlayer pp : lst) { + pp.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by wildcard"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + pp.getName()); + } + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added wildcard '" + ChatColor.WHITE + w + ChatColor.GREEN + "' to the ban list"); + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Wildcard '" + ChatColor.WHITE + w + ChatColor.RED + "' is already banned"); + } + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Canceled ban"); + } + p0.getAttachment().remove("banWildcardWaitingToAdd"); + p0.getAttachment().remove("banWildcardWaitingToKick"); + } + return; + } + if(p1.length != 1) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "How to use: " + ChatColor.WHITE + (replaceBukkit ? "/ban-wildcard" : "/eag-ban-wildcard") + " "); + return; + } + p1[0] = p1[0].toLowerCase(); + String s = p1[0]; + boolean startStar = s.startsWith("*"); + if(startStar) { + s = s.substring(1); + } + boolean endStar = s.endsWith("*"); + if(endStar) { + s = s.substring(0, s.length() - 1); + } + if(!startStar && !endStar) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "'" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is not a wildcard, try '" + + ChatColor.WHITE + "*" + p1[0] + ChatColor.RED + "' or '" + ChatColor.WHITE + p1[0] + "*" + ChatColor.RED + "' or '" + ChatColor.WHITE + + "*" + p1[0] + "*" + ChatColor.RED + "' instead"); + return; + } + boolean isSenderGonnaGetKicked = false; + List usersThatAreGonnaBeKicked = new ArrayList(); + for(ProxiedPlayer pp : BungeeCord.getInstance().getPlayers()) { + String n = pp.getName().toLowerCase(); + if(startStar && endStar) { + if(n.contains(s)) { + usersThatAreGonnaBeKicked.add(pp); + if(pp.getName().equalsIgnoreCase(p0.getName())) { + isSenderGonnaGetKicked = true; + break; + } + } + }else if(startStar) { + if(n.endsWith(s)) { + usersThatAreGonnaBeKicked.add(pp); + if(pp.getName().equalsIgnoreCase(p0.getName())) { + isSenderGonnaGetKicked = true; + break; + } + } + }else if(endStar) { + if(n.startsWith(s)) { + usersThatAreGonnaBeKicked.add(pp); + if(pp.getName().equalsIgnoreCase(p0.getName())) { + isSenderGonnaGetKicked = true; + break; + } + } + } + } + if(isSenderGonnaGetKicked) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "banning wildcard '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is gonna ban your own username"); + return; + } + if(usersThatAreGonnaBeKicked.size() > 1) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "WARNING: banning wildcard '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is gonna ban " + + ChatColor.WHITE + usersThatAreGonnaBeKicked.size() + ChatColor.RED + " players off of your server"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-wildcard" : "/eag-ban-wildcard") + " confirm" + ChatColor.RED + " to continue, or type " + + ChatColor.WHITE + (replaceBukkit ? "/ban-wildcard" : "/eag-ban-wildcard") + " cancel" + ChatColor.RED + " to cancel"); + p0.getAttachment().put("banWildcardWaitingToKick", usersThatAreGonnaBeKicked); + p0.getAttachment().put("banWildcardWaitingToAdd", p1[0]); + }else { + if(BanList.banWildcard(p1[0])) { + if(usersThatAreGonnaBeKicked.size() > 0) { + usersThatAreGonnaBeKicked.get(0).disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by wildcard"); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + usersThatAreGonnaBeKicked.get(0).getName()); + } + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added wildcard '" + ChatColor.WHITE + p1[0] + ChatColor.GREEN + "' to the ban list"); + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Wildcard '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is already banned"); + } + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalCheckBan.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalCheckBan.java new file mode 100644 index 0000000..be2bd85 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalCheckBan.java @@ -0,0 +1,56 @@ +package net.md_5.bungee.command; + +import java.net.InetAddress; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.BanList; +import net.md_5.bungee.eaglercraft.BanList.BanCheck; +import net.md_5.bungee.eaglercraft.BanList.BanState; + +public class CommandGlobalCheckBan extends Command { + + private final boolean replaceBukkit; + + public CommandGlobalCheckBan(boolean replaceBukkit) { + super(replaceBukkit ? "banned" : "eag-bannned", "bungeecord.command.eag.banned", replaceBukkit ? new String[] { "eag-banned", "isbanned", "e-banned", "gbanned", "eag-isbanned", "e-isbanned", "gisbanned" } : + new String[] { "e-banned", "gbanned", "eag-isbanned", "e-isbanned", "gisbanned" }); + this.replaceBukkit = replaceBukkit; + } + + @Override + public void execute(CommandSender p0, String[] p1) { + if(p1.length != 1) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To check if a player or IP is banned, use: " + ChatColor.WHITE + (replaceBukkit ? "/banned" : "/eag-banned") + " "); + }else { + BanCheck bc = BanList.checkBanned(p1[0]); + if(!bc.isBanned()) { + try { + InetAddress addr = InetAddress.getByName(p1[0]); + bc = BanList.checkIpBanned(addr); + if(bc.isBanned()) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "IP address '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is banned by: " + + "'" + ChatColor.WHITE + bc.match + ChatColor.RED + "' " + ChatColor.YELLOW + "(" + bc.string + ")"); + return; + } + }catch(Throwable t) { + // no + } + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Player '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' has not been banned"); + }else { + if(bc.reason == BanState.USER_BANNED) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Player '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is banned by username, reason: " + + ChatColor.YELLOW + "\"" + bc.string + "\""); + }else if(bc.reason == BanState.WILDCARD_BANNED) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Player '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is banned by wildcard: " + + ChatColor.WHITE + "\"" + bc.match + "\""); + }else if(bc.reason == BanState.REGEX_BANNED) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Player '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is banned by regex: " + + ChatColor.WHITE + "\"" + bc.match + "\""); + } + } + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalListBan.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalListBan.java new file mode 100644 index 0000000..05d830a --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalListBan.java @@ -0,0 +1,64 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.BanList; + +public class CommandGlobalListBan extends Command { + + private final boolean replaceBukkit; + + public CommandGlobalListBan(boolean replaceBukkit) { + super(replaceBukkit ? "banlist" : "eag-banlist", "bungeecord.command.eag.banlist", replaceBukkit ? new String[] { "eag-banlist", "gbanlist", "e-banlist", + "gbanlist" } : new String[] { "gbanlist", "e-banlist" }); + this.replaceBukkit = replaceBukkit; + } + + @Override + public void execute(CommandSender p0, String[] p1) { + if(p1.length == 0 || (p1.length == 1 && (p1[0].equalsIgnoreCase("user") || p1[0].equalsIgnoreCase("username") + || p1[0].equalsIgnoreCase("users") || p1[0].equalsIgnoreCase("usernames")))) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Players banned by username: " + ChatColor.WHITE + BanList.listAllBans()); + return; + }else if(p1.length == 1) { + if(p1[0].equalsIgnoreCase("regex") || p1[0].equalsIgnoreCase("regexes")) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Regex ban list: " + ChatColor.WHITE + BanList.listAllRegexBans()); + return; + }else if(p1[0].equalsIgnoreCase("wildcard") || p1[0].equalsIgnoreCase("wildcards")) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Wildcard ban list: " + ChatColor.WHITE + BanList.listAllWildcardBans()); + return; + }else if(p1[0].equalsIgnoreCase("ip") || p1[0].equalsIgnoreCase("ips")) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To list IP bans, use: " + ChatColor.WHITE + (replaceBukkit ? "/banlist" : "/eag-banlist") + " ip [v4|v6]"); + return; + } + }else if(p1.length > 1 && p1.length <= 3 && (p1[0].equalsIgnoreCase("ip") || p1[0].equalsIgnoreCase("ips"))) { + int addrOrNetmask = 0; + if(p1[1].equalsIgnoreCase("addr") || p1[1].equalsIgnoreCase("addrs")) { + addrOrNetmask = 1; + }else if(p1[1].equalsIgnoreCase("netmask") || p1[1].equalsIgnoreCase("netmasks")) { + addrOrNetmask = 2; + } + if(addrOrNetmask > 0) { + boolean yes = false; + if(p1.length == 2 || (p1.length == 3 && (p1[2].equalsIgnoreCase("v4") || p1[2].equals("4")))) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "IPv4 " + (addrOrNetmask == 2 ? "netmask" : "address") + " ban list: " + ChatColor.WHITE + BanList.listAllIPBans(false, addrOrNetmask == 2)); + yes = true; + } + if(p1.length == 2 || (p1.length == 3 && (p1[2].equalsIgnoreCase("v6") || p1[2].equals("6")))) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "IPv6 " + (addrOrNetmask == 2 ? "netmask" : "address") + " ban list: " + ChatColor.WHITE + BanList.listAllIPBans(true, addrOrNetmask == 2)); + yes = true; + } + if(yes) { + return; + } + } + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To list IP bans, use: " + ChatColor.WHITE + (replaceBukkit ? "/banlist" : "/eag-banlist") + " ip [v4|v6]"); + return; + } + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To list all user bans, use: " + ChatColor.WHITE + (replaceBukkit ? "/banlist" : "/eag-banlist")); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To list ips, regexes, and wildcards, use: " + ChatColor.WHITE + (replaceBukkit ? "/banlist" : "/eag-banlist") + " "); + return; + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalUnban.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalUnban.java new file mode 100644 index 0000000..aac2039 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandGlobalUnban.java @@ -0,0 +1,56 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.eaglercraft.BanList; + +public class CommandGlobalUnban extends Command { + + private final boolean replaceBukkit; + + public CommandGlobalUnban(boolean replaceBukkit) { + super(replaceBukkit ? "unban" : "eag-unban", "bungeecord.command.eag.unban", replaceBukkit ? new String[] {"eag-unban", "e-unban", "gunban"} :new String[] {"e-unban", "gunban"}); + this.replaceBukkit = replaceBukkit; + } + + @Override + public void execute(CommandSender p0, String[] p1) { + if(p1.length != 2 || (!p1[0].equalsIgnoreCase("user") && !p1[0].equalsIgnoreCase("username") && !p1[0].equalsIgnoreCase("player") + && !p1[0].equalsIgnoreCase("wildcard") && !p1[0].equalsIgnoreCase("regex") && !p1[0].equalsIgnoreCase("ip"))) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To unban a player, use: " + ChatColor.WHITE + "/" + (replaceBukkit?"":"eag-") + "unban user "); + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To unban an ip/wildcard/regex, use: " + ChatColor.WHITE + "/" + (replaceBukkit?"":"eag-") + "unban "); + return; + } + if(p1[0].equalsIgnoreCase("user") || p1[0].equalsIgnoreCase("username") || p1[0].equalsIgnoreCase("player")) { + if(BanList.unban(p1[1])) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "User '" + ChatColor.WHITE + p1[1] + ChatColor.GREEN + "' was unbanned"); + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "User '" + ChatColor.WHITE + p1[1] + ChatColor.RED + "' is not banned"); + } + }else if(p1[0].equalsIgnoreCase("ip")) { + try { + if(BanList.unbanIP(p1[1])) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "IP '" + ChatColor.WHITE + p1[1] + ChatColor.GREEN + "' was unbanned"); + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "IP '" + ChatColor.WHITE + p1[1] + ChatColor.RED + "' is not banned"); + } + }catch(Throwable t) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "IP address '" + ChatColor.WHITE + p1[1] + ChatColor.RED + "' is invalid: " + t.getMessage()); + } + }else if(p1[0].equalsIgnoreCase("wildcard")) { + if(BanList.unbanWildcard(p1[1])) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Wildcard '" + ChatColor.WHITE + p1[1] + ChatColor.GREEN + "' was unbanned"); + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Wildcard '" + ChatColor.WHITE + p1[1] + ChatColor.RED + "' is not banned"); + } + }else if(p1[0].equalsIgnoreCase("regex")) { + if(BanList.unbanRegex(p1[1])) { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Regex '" + ChatColor.WHITE + p1[1] + ChatColor.GREEN + "' was unbanned"); + }else { + p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Regex '" + ChatColor.WHITE + p1[1] + ChatColor.RED + "' is not banned"); + } + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandIP.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandIP.java new file mode 100644 index 0000000..ac30aa6 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandIP.java @@ -0,0 +1,34 @@ +package net.md_5.bungee.command; + +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.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; + +public class CommandIP extends Command +{ + + public CommandIP() + { + super( "ip", "bungeecord.command.ip" ); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + if ( args.length < 1 ) + { + sender.sendMessage( ChatColor.RED + "Please follow this command by a user name" ); + return; + } + ProxiedPlayer user = ProxyServer.getInstance().getPlayer( args[0] ); + if ( user == null ) + { + sender.sendMessage( ChatColor.RED + "That user is not online" ); + } else + { + sender.sendMessage( ChatColor.BLUE + "IP of " + args[0] + " is " + user.getAddress() ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandList.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandList.java new file mode 100644 index 0000000..ae65f9d --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandList.java @@ -0,0 +1,47 @@ +package net.md_5.bungee.command; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import net.md_5.bungee.Util; +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.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; + +/** + * Command to list all players connected to the proxy. + */ +public class CommandList extends Command +{ + + public CommandList() + { + super( "glist", "bungeecord.command.list" ); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + for ( ServerInfo server : ProxyServer.getInstance().getServers().values() ) + { + if ( !server.canAccess( sender ) ) + { + continue; + } + + List players = new ArrayList<>(); + for ( ProxiedPlayer player : server.getPlayers() ) + { + players.add( player.getDisplayName() ); + } + Collections.sort( players, String.CASE_INSENSITIVE_ORDER ); + + sender.sendMessage( ProxyServer.getInstance().getTranslation( "command_list", server.getName(), server.getPlayers().size(), Util.format( players, ChatColor.RESET + ", " ) ) ); + } + + sender.sendMessage( ProxyServer.getInstance().getTranslation( "total_players", ProxyServer.getInstance().getOnlineCount() ) ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandPerms.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandPerms.java new file mode 100644 index 0000000..610825c --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandPerms.java @@ -0,0 +1,34 @@ +package net.md_5.bungee.command; + +import java.util.HashSet; +import java.util.Set; +import net.md_5.bungee.Util; +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.plugin.Command; + +public class CommandPerms extends Command +{ + + public CommandPerms() + { + super( "perms" ); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + Set permissions = new HashSet<>(); + for ( String group : sender.getGroups() ) + { + permissions.addAll( ProxyServer.getInstance().getConfigurationAdapter().getPermissions( group ) ); + } + sender.sendMessage( ChatColor.GOLD + "You have the following groups: " + Util.csv( sender.getGroups() ) ); + + for ( String permission : permissions ) + { + sender.sendMessage( ChatColor.BLUE + "- " + permission ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandReload.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandReload.java new file mode 100644 index 0000000..437f010 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandReload.java @@ -0,0 +1,25 @@ +package net.md_5.bungee.command; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; + +public class CommandReload extends Command +{ + + public CommandReload() + { + super( "greload", "bungeecord.command.reload" ); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + BungeeCord.getInstance().config.load(); + BungeeCord.getInstance().stopListeners(); + BungeeCord.getInstance().startListeners(); + sender.sendMessage( ChatColor.BOLD.toString() + ChatColor.RED.toString() + "BungeeCord has been reloaded." + + " This is NOT advisable and you will not be supported with any issues that arise! Please restart BungeeCord ASAP." ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandSend.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandSend.java new file mode 100644 index 0000000..527914f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandSend.java @@ -0,0 +1,72 @@ +package net.md_5.bungee.command; + +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.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; + +public class CommandSend extends Command +{ + + public CommandSend() + { + super( "send", "bungeecord.command.send" ); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + if ( args.length != 2 ) + { + sender.sendMessage( ChatColor.RED + "Not enough arguments, usage: /send " ); + return; + } + ServerInfo target = ProxyServer.getInstance().getServerInfo( args[1] ); + if ( target == null ) + { + sender.sendMessage( ProxyServer.getInstance().getTranslation( "no_server" ) ); + return; + } + + if ( args[0].equalsIgnoreCase( "all" ) ) + { + for ( ProxiedPlayer p : ProxyServer.getInstance().getPlayers() ) + { + summon( p, target, sender ); + } + } else if ( args[0].equalsIgnoreCase( "current" ) ) + { + if ( !( sender instanceof ProxiedPlayer ) ) + { + sender.sendMessage( ChatColor.RED + "Only in game players can use this command" ); + return; + } + ProxiedPlayer player = (ProxiedPlayer) sender; + for ( ProxiedPlayer p : player.getServer().getInfo().getPlayers() ) + { + summon( p, target, sender ); + } + } else + { + ProxiedPlayer player = ProxyServer.getInstance().getPlayer( args[0] ); + if ( player == null ) + { + sender.sendMessage( ChatColor.RED + "That player is not online" ); + return; + } + summon( player, target, sender ); + } + sender.sendMessage( ChatColor.GREEN + "Successfully summoned player(s)" ); + } + + private void summon(ProxiedPlayer player, ServerInfo target, CommandSender sender) + { + if ( player.getServer() != null && !player.getServer().getInfo().equals( target ) ) + { + player.connect( target ); + player.sendMessage( ChatColor.GOLD + "Summoned to " + target.getName() + " by " + sender.getName() ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandServer.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandServer.java new file mode 100644 index 0000000..5ee09a3 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/CommandServer.java @@ -0,0 +1,88 @@ +package net.md_5.bungee.command; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import java.util.Collections; +import java.util.Map; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.api.plugin.TabExecutor; + +/** + * Command to list and switch a player between available servers. + */ +public class CommandServer extends Command implements TabExecutor +{ + + public CommandServer() + { + super( "server", "bungeecord.command.server" ); + } + + @Override + public void execute(CommandSender sender, String[] args) + { + if ( !( sender instanceof ProxiedPlayer ) ) + { + return; + } + ProxiedPlayer player = (ProxiedPlayer) sender; + Map servers = ProxyServer.getInstance().getServers(); + if ( args.length == 0 ) + { + player.sendMessage( ProxyServer.getInstance().getTranslation( "current_server" ) + player.getServer().getInfo().getName() ); + + StringBuilder serverList = new StringBuilder(); + for ( ServerInfo server : servers.values() ) + { + if ( server.canAccess( player ) ) + { + serverList.append( server.getName() ); + serverList.append( ", " ); + } + } + if ( serverList.length() != 0 ) + { + serverList.setLength( serverList.length() - 2 ); + } + player.sendMessage( ProxyServer.getInstance().getTranslation( "server_list" ) + serverList.toString() ); + } else + { + ServerInfo server = servers.get( args[0] ); + if ( server == null ) + { + player.sendMessage( ProxyServer.getInstance().getTranslation( "no_server" ) ); + } else if ( !server.canAccess( player ) ) + { + player.sendMessage( ProxyServer.getInstance().getTranslation( "no_server_permission" ) ); + } else + { + player.connect( server ); + } + } + } + + @Override + public Iterable onTabComplete(final CommandSender sender, String[] args) + { + return ( args.length != 0 ) ? Collections.EMPTY_LIST : Iterables.transform( Iterables.filter( ProxyServer.getInstance().getServers().values(), new Predicate() + { + @Override + public boolean apply(ServerInfo input) + { + return input.canAccess( sender ); + } + } ), new Function() + { + @Override + public String apply(ServerInfo input) + { + return input.getName(); + } + } ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java new file mode 100644 index 0000000..35670a1 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java @@ -0,0 +1,83 @@ +package net.md_5.bungee.command; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; + +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; + +/** + * Command sender representing the proxy console. + */ +public class ConsoleCommandSender implements CommandSender +{ + private static final Map attachment = new WeakHashMap(); + + private static final ConsoleCommandSender instance = new ConsoleCommandSender(); + + private ConsoleCommandSender() + { + } + + public static ConsoleCommandSender getInstance(){ + return instance; + } + + @Override + public void sendMessage(String message) + { + ProxyServer.getInstance().getLogger().info( message ); + } + + @Override + public void sendMessages(String... messages) + { + for ( String message : messages ) + { + sendMessage( message ); + } + } + + @Override + public String getName() + { + return "CONSOLE"; + } + + @Override + public Collection getGroups() + { + return Collections.emptySet(); + } + + @Override + public void addGroups(String... groups) + { + throw new UnsupportedOperationException( "Console may not have groups" ); + } + + @Override + public void removeGroups(String... groups) + { + throw new UnsupportedOperationException( "Console may not have groups" ); + } + + @Override + public boolean hasPermission(String permission) + { + return true; + } + + @Override + public void setPermission(String permission, boolean value) + { + throw new UnsupportedOperationException( "Console has all permissions" ); + } + + @Override + public Map getAttachment() { + return attachment; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/command/PlayerCommand.java b/eaglerbungee/src/main/java/net/md_5/bungee/command/PlayerCommand.java new file mode 100644 index 0000000..a0c9164 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/command/PlayerCommand.java @@ -0,0 +1,45 @@ +package net.md_5.bungee.command; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.api.plugin.TabExecutor; + +public abstract class PlayerCommand extends Command implements TabExecutor +{ + + public PlayerCommand(String name) + { + super( name ); + } + + public PlayerCommand(String name, String permission, String... aliases) + { + super( name, permission, aliases ); + } + + @Override + public Iterable onTabComplete(CommandSender sender, String[] args) + { + final String lastArg = ( args.length > 0 ) ? args[args.length - 1] : ""; + return Iterables.transform( Iterables.filter( ProxyServer.getInstance().getPlayers(), new Predicate() + { + @Override + public boolean apply(ProxiedPlayer player) + { + return player.getName().startsWith( lastArg ); + } + } ), new Function() + { + @Override + public String apply(ProxiedPlayer player) + { + return player.getName(); + } + } ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/config/Configuration.java b/eaglerbungee/src/main/java/net/md_5/bungee/config/Configuration.java new file mode 100644 index 0000000..1f4102f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/config/Configuration.java @@ -0,0 +1,181 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.config; + +import java.util.Collection; +import java.util.Map; +import java.util.UUID; + +import com.google.common.base.Preconditions; + +import gnu.trove.map.TMap; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.AuthServiceInfo; +import net.md_5.bungee.api.config.ConfigurationAdapter; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.eaglercraft.EaglercraftBungee; +import net.md_5.bungee.util.CaseInsensitiveMap; + +public class Configuration { + private int timeout; + private String uuid; + private Collection listeners; + private TMap servers; + private AuthServiceInfo authInfo; + private boolean onlineMode; + private boolean voiceEnabled; + private boolean protocolSupport; + private String tokenVerify; + private int playerLimit; + private String name; + private boolean showBanType; + private boolean blacklistOfflineDownload; + private boolean blacklistReplits; + private boolean blacklistOriginless; + private boolean simpleWhitelistEnabled; + private boolean acceptBukkitConsoleCommandPacket; + private Collection disabledCommands; + private Collection iceServers; + private boolean bungeeOnBungee; + + public Configuration() { + this.timeout = 30000; + this.uuid = UUID.randomUUID().toString(); + this.onlineMode = true; + this.playerLimit = -1; + } + + public void load() { + final ConfigurationAdapter adapter = ProxyServer.getInstance().getConfigurationAdapter(); + adapter.load(); + this.listeners = adapter.getListeners(); + this.timeout = adapter.getInt("timeout", this.timeout); + this.uuid = adapter.getString("stats", this.uuid); + if(this.uuid.equalsIgnoreCase("595698b3-9c36-4e86-b1ee-cb3027038f41")) { + this.uuid = UUID.randomUUID().toString(); + System.err.println("Notice: this server has the stats UUID \"595698b3-9c36-4e86-b1ee-cb3027038f41\" which is a known duplicate"); + System.err.println("It has been updated to \"" + this.uuid + "\". This is not an error"); + adapter.getMap().put("stats", this.uuid); + adapter.forceSave(); + } + this.authInfo = adapter.getAuthSettings(); + this.onlineMode = false; + this.voiceEnabled = adapter.getBoolean("voice_enabled", true); + this.protocolSupport = adapter.getBoolean("protocol_support_fix", false); + this.tokenVerify = adapter.getString("token_verify", ""); + this.playerLimit = adapter.getInt("player_limit", this.playerLimit); + this.name = adapter.getString("server_name", EaglercraftBungee.name + " Server"); + this.showBanType = adapter.getBoolean("display_ban_type_on_kick", false); + this.blacklistOfflineDownload = adapter.getBoolean("origin_blacklist_block_offline_download", false); + this.blacklistReplits = adapter.getBoolean("origin_blacklist_block_replit_clients", false); + adapter.getMap().remove("origin_blacklist_block_missing_origin_header"); + this.blacklistOriginless = adapter.getBoolean("origin_blacklist_block_invalid_origin_header", true); + this.simpleWhitelistEnabled = adapter.getBoolean("origin_blacklist_use_simple_whitelist", false); + this.acceptBukkitConsoleCommandPacket = adapter.getBoolean("accept_bukkit_console_command_packets", false); + this.bungeeOnBungee = adapter.getBoolean("bungee_on_bungee", false); + this.disabledCommands = adapter.getDisabledCommands(); + this.iceServers = adapter.getICEServers(); + Preconditions.checkArgument(this.listeners != null && !this.listeners.isEmpty(), (Object) "No listeners defined."); + final Map newServers = adapter.getServers(); + Preconditions.checkArgument(newServers != null && !newServers.isEmpty(), (Object) "No servers defined"); + if (this.servers == null) { + this.servers = (TMap) new CaseInsensitiveMap(newServers); + } else { + for (final ServerInfo oldServer : this.servers.values()) { + Preconditions.checkArgument(newServers.containsValue(oldServer), "Server %s removed on reload!", new Object[] { oldServer.getName() }); + } + for (final Map.Entry newServer : newServers.entrySet()) { + if (!this.servers.containsValue(newServer.getValue())) { + this.servers.put(newServer.getKey(), newServer.getValue()); + } + } + } + for (final ListenerInfo listener : this.listeners) { + Preconditions.checkArgument(this.servers.containsKey((Object) listener.getDefaultServer()), "Default server %s is not defined", new Object[] { listener.getDefaultServer() }); + } + } + + public int getTimeout() { + return this.timeout; + } + + public String getUuid() { + return this.uuid; + } + + public Collection getListeners() { + return this.listeners; + } + + public TMap getServers() { + return this.servers; + } + + public boolean isOnlineMode() { + return this.onlineMode; + } + + public int getPlayerLimit() { + return this.playerLimit; + } + + public AuthServiceInfo getAuthInfo() { + return authInfo; + } + + public boolean getVoiceEnabled() { + return voiceEnabled; + } + + public boolean getProtocolSupport() { + return protocolSupport; + } + + public String getTokenVerify() { + return tokenVerify; + } + + public String getServerName() { + return name; + } + + public boolean shouldShowBanType() { + return this.showBanType; + } + + public boolean shouldBlacklistOfflineDownload() { + return blacklistOfflineDownload; + } + + public boolean shouldBlacklistReplits() { + return blacklistReplits; + } + + public boolean shouldBlacklistOriginless() { + return blacklistOriginless; + } + + public boolean isSimpleWhitelistEnabled() { + return simpleWhitelistEnabled; + } + + public boolean shouldAcceptBukkitConsoleCommandPacket() { + return acceptBukkitConsoleCommandPacket; + } + + public Collection getDisabledCommands() { + return disabledCommands; + } + + public Collection getICEServers() { + return iceServers; + } + + public boolean allowBungeeOnBungee() { + return bungeeOnBungee; + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/config/YamlConfig.java b/eaglerbungee/src/main/java/net/md_5/bungee/config/YamlConfig.java new file mode 100644 index 0000000..f65e110 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/config/YamlConfig.java @@ -0,0 +1,400 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; + +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.Util; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.AuthServiceInfo; +import net.md_5.bungee.api.config.ConfigurationAdapter; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.config.MOTDCacheConfiguration; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.config.TexturePackInfo; +import net.md_5.bungee.api.tab.TabListHandler; +import net.md_5.bungee.eaglercraft.RedirectServerInfo; +import net.md_5.bungee.eaglercraft.WebSocketRateLimiter; +import net.md_5.bungee.tab.Global; +import net.md_5.bungee.tab.GlobalPing; +import net.md_5.bungee.tab.ServerUnique; +import net.md_5.bungee.util.CaseInsensitiveMap; + +public class YamlConfig implements ConfigurationAdapter { + private Yaml yaml; + private Map config; + private final File file; + + public YamlConfig() { + this.file = new File("config.yml"); + } + + @Override + public void load() { + try { + this.file.createNewFile(); + final DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + this.yaml = new Yaml(options); + try (final InputStream is = new FileInputStream(this.file)) { + this.config = (Map) this.yaml.load(is); + } + if (this.config == null) { + this.config = (Map) new CaseInsensitiveMap(); + } else { + this.config = (Map) new CaseInsensitiveMap(this.config); + } + } catch (IOException ex) { + throw new RuntimeException("Could not load configuration!", ex); + } + final Map> permissions = this.get("permissions", new HashMap>()); + if (permissions.isEmpty()) { + permissions.put("default", Arrays.asList("bungeecord.command.server", "bungeecord.command.list", "bungeecord.command.eag.domain", "bungeecord.command.eag.changepassword")); + permissions.put("admin", Arrays.asList("bungeecord.command.alert", "bungeecord.command.end", "bungeecord.command.ip", "bungeecord.command.reload", + "bungeecord.command.eag.ban", "bungeecord.command.eag.banwildcard", "bungeecord.command.eag.banip", "bungeecord.command.eag.banregex", + "bungeecord.command.eag.reloadban", "bungeecord.command.eag.banned", "bungeecord.command.eag.banlist", "bungeecord.command.eag.unban", "bungeecord.command.eag.ratelimit", + "bungeecord.command.eag.blockdomain", "bungeecord.command.eag.blockdomainname", "bungeecord.command.eag.unblockdomain")); + } else if (this.get("authservice", new HashMap()).isEmpty() && permissions.containsKey("default") && !permissions.get("default").contains("bungeecord.command.eag.changepassword")) { + permissions.get("default").add("bungeecord.command.eag.changepassword"); + } + this.get("groups", new HashMap()); + } + + private T get(final String path, final T def) { + return this.get(path, def, this.config); + } + + private T get(final String path, final T def, final Map submap) { + final int index = path.indexOf(46); + if (index == -1) { + Object val = submap.get(path); + if (val == null && def != null) { + val = def; + submap.put(path, def); + this.save(); + } + return (T) val; + } + final String first = path.substring(0, index); + final String second = path.substring(index + 1, path.length()); + Map sub = (Map) submap.get(first); + if (sub == null) { + sub = new LinkedHashMap(); + submap.put(first, sub); + } + return (T) this.get(second, (Object) def, sub); + } + + private void save() { + try (final FileWriter wr = new FileWriter(this.file)) { + this.yaml.dump((Object) this.config, (Writer) wr); + } catch (IOException ex) { + ProxyServer.getInstance().getLogger().log(Level.WARNING, "Could not save config", ex); + } + } + + @Override + public int getInt(final String path, final int def) { + return this.get(path, def); + } + + @Override + public String getString(final String path, final String def) { + return this.get(path, def); + } + + @Override + public boolean getBoolean(final String path, final boolean def) { + return this.get(path, def); + } + + @Override + public Map getServers() { + final Map base = this.get("servers", (Map) Collections.singletonMap("lobby", new HashMap())); + final Map ret = new HashMap(); + for (final Map.Entry entry : base.entrySet()) { + final Map val = entry.getValue(); + final String name = entry.getKey(); + if(val.containsKey("redirect")) { + final String addr = this.get("redirect", "ws://someOtherServer/", val); + final boolean restricted = this.get("restricted", false, val); + final ServerInfo info = new RedirectServerInfo(name, addr, restricted); + ret.put(name, info); + }else { + final String addr = this.get("address", "localhost:25501", val); + final boolean restricted = this.get("restricted", false, val); + final InetSocketAddress address = Util.getAddr(addr); + final ServerInfo info = ProxyServer.getInstance().constructServerInfo(name, address, restricted); + ret.put(name, info); + } + } + return ret; + } + + @Override + public Collection getListeners() { + final Collection base = this.get("listeners", (Collection) Arrays.asList(new HashMap())); + final Map forcedDef = new HashMap(); + //forcedDef.put("pvp.md-5.net", "pvp"); + final Collection ret = new HashSet(); + for (final Map val : base) { + String motd = this.get("motd", null, val); + if(motd != null) { + val.remove("motd"); + } + motd = this.get("motd1", motd, val); + if(motd == null) { + motd = this.get("motd1", "&6An Eaglercraft server", val); + } + motd = ChatColor.translateAlternateColorCodes('&', motd); + String motd2 = this.get("motd2", null, val); + if(motd2 != null && motd2.length() > 0) { + motd = motd + "\n" + ChatColor.translateAlternateColorCodes('&', motd2); + } + final int maxPlayers = this.get("max_players", 60, val); + final String defaultServer = this.get("default_server", "lobby", val); + final String fallbackServer = this.get("fallback_server", defaultServer, val); + final boolean forceDefault = this.get("force_default_server", true, val); + final boolean websocket = this.get("websocket", true, val); + final boolean forwardIp = this.get("forward_ip", false, val); + final String forwardIpHeader = this.get("forward_ip_header", "X-Real-IP", val); + final String host = this.get("host", "0.0.0.0:25565", val); + final String javaHost = this.get("java_host", "null", val); + final int tabListSize = this.get("tab_size", 60, val); + final InetSocketAddress address = Util.getAddr(host); + final InetSocketAddress javaAddress = (javaHost.equalsIgnoreCase("null") || javaHost == null) ? null : Util.getAddr(javaHost); + final Map forced = (Map) new CaseInsensitiveMap(this.get("forced_hosts", forcedDef, val)); + final String textureURL = this.get("texture_url", (String) null, val); + final int textureSize = this.get("texture_size", 16, val); + final TexturePackInfo texture = (textureURL == null) ? null : new TexturePackInfo(textureURL, textureSize); + final String tabListName = this.get("tab_list", "GLOBAL_PING", val); + final String serverIcon = this.get("server_icon", "server-icon.png", val); + final boolean allowMOTD = this.get("allow_motd", true, val); + final boolean allowQuery = this.get("allow_query", true, val); + final MOTDCacheConfiguration cacheConfig = readCacheConfiguration(this.get("request_motd_cache", new HashMap(), val)); + + WebSocketRateLimiter ratelimitIP = null; + WebSocketRateLimiter ratelimitLogin = null; + WebSocketRateLimiter ratelimitMOTD = null; + WebSocketRateLimiter ratelimitQuery = null; + final Map rateLimits = this.get("ratelimit", new HashMap(), val); + final Map ratelimitIPConfig = this.get("ip", new HashMap(), rateLimits); + final Map ratelimitLoginConfig = this.get("login", new HashMap(), rateLimits); + final Map ratelimitMOTDConfig = this.get("motd", new HashMap(), rateLimits); + final Map ratelimitQueryConfig = this.get("query", new HashMap(), rateLimits); + + if(this.get("enable", true, ratelimitIPConfig)) { + ratelimitIP = new WebSocketRateLimiter( + this.get("period", 90, ratelimitIPConfig), + this.get("limit", 60, ratelimitIPConfig), + this.get("limit_lockout", 80, ratelimitIPConfig), + this.get("lockout_duration", 1200, ratelimitIPConfig), + this.get("exceptions", new ArrayList(), ratelimitIPConfig) + ); + } + if(this.get("enable", true, ratelimitLoginConfig)) { + ratelimitLogin = new WebSocketRateLimiter( + this.get("period", 50, ratelimitLoginConfig), + this.get("limit", 5, ratelimitLoginConfig), + this.get("limit_lockout", 10, ratelimitLoginConfig), + this.get("lockout_duration", 300, ratelimitLoginConfig), + this.get("exceptions", new ArrayList(), ratelimitLoginConfig) + ); + } + if(this.get("enable", true, ratelimitMOTDConfig)) { + ratelimitMOTD = new WebSocketRateLimiter( + this.get("period", 30, ratelimitMOTDConfig), + this.get("limit", 5, ratelimitMOTDConfig), + this.get("limit_lockout", 15, ratelimitMOTDConfig), + this.get("lockout_duration", 1200, ratelimitMOTDConfig), + this.get("exceptions", new ArrayList(), ratelimitMOTDConfig) + ); + } + if(this.get("enable", true, ratelimitQueryConfig)) { + ratelimitQuery = new WebSocketRateLimiter( + this.get("period", 90, ratelimitQueryConfig), + this.get("limit", 60, ratelimitQueryConfig), + this.get("limit_lockout", 80, ratelimitQueryConfig), + this.get("lockout_duration", 1200, ratelimitQueryConfig), + this.get("exceptions", new ArrayList(), ratelimitQueryConfig) + ); + } + + + DefaultTabList value = DefaultTabList.valueOf(tabListName.toUpperCase()); + if (value == null) { + value = DefaultTabList.GLOBAL_PING; + } + ret.add(new ListenerInfo(host, address, javaAddress, forwardIpHeader, motd, maxPlayers, tabListSize, defaultServer, + fallbackServer, forceDefault, websocket, forwardIp, forced, texture, value.clazz, serverIcon, cacheConfig, + allowMOTD, allowQuery, ratelimitIP, ratelimitLogin, ratelimitMOTD, ratelimitQuery)); + } + return ret; + } + + private MOTDCacheConfiguration readCacheConfiguration(Map val) { + final int ttl = this.get("cache_ttl", 7200, val); + final boolean anim = this.get("online_server_list_animation", false, val); + final boolean results = this.get("online_server_list_results", true, val); + final boolean trending = this.get("online_server_list_trending", true, val); + final boolean portfolios = this.get("online_server_list_portfolios", false, val); + return new MOTDCacheConfiguration(ttl, anim, results, trending, portfolios); + } + + @Override + public Collection getGroups(final String player) { + final Collection groups = this.get("groups." + player, (Collection) null); + final Collection ret = (groups == null) ? new HashSet() : new HashSet(groups); + ret.add("default"); + return ret; + } + + @Override + public Collection getPermissions(final String group) { + return this.get("permissions." + group, (Collection) Collections.EMPTY_LIST); + } + + private enum DefaultTabList { + GLOBAL((Class) Global.class), GLOBAL_PING((Class) GlobalPing.class), SERVER((Class) ServerUnique.class); + + private final Class clazz; + + private DefaultTabList(final Class clazz) { + this.clazz = clazz; + } + } + + public AuthServiceInfo getAuthSettings() { + final Map auth = this.get("authservice", new HashMap()); + final List defaultJoinMessages = new ArrayList(); + defaultJoinMessages.add("&3Welcome to my &aEaglercraftBungee &3server!"); + return new AuthServiceInfo(this.get("enabled", false, auth), this.get("register_enabled", true, auth), this.get("authfile", "auths.db", auth), + this.get("ip_limit", 0, auth), this.get("join_messages", defaultJoinMessages, auth), this.get("login_timeout", 30, auth)); + } + + public Map getMap() { + return config; + } + + public void forceSave() { + this.save(); + } + + public Collection getBlacklistURLs() { + boolean blacklistEnable = this.getBoolean("enable_web_origin_blacklist", true); + if(!blacklistEnable) { + return null; + } + Collection c = this.get("origin_blacklist_subscriptions", null); + if(c == null) { + c = new ArrayList(); + c.add("https://g.lax1dude.net/eaglercraft/origin_blacklist.txt"); + c.add("https://raw.githubusercontent.com/LAX1DUDE/eaglercraft/main/stable-download/origin_blacklist.txt"); + c = this.get("origin_blacklist_subscriptions", c); + }else { + if(c.remove("https://g.eags.us/eaglercraft/origin_blacklist.txt")) { + c.add("https://g.lax1dude.net/eaglercraft/origin_blacklist.txt"); + this.save(); + BungeeCord.getInstance().getLogger().warning("Your origin blacklist has been patched to use g.lax1dude.net instead"); + } + } + return c; + } + + public Collection getBlacklistSimpleWhitelist() { + Collection c = this.get("origin_blacklist_simple_whitelist", null); + if(c == null) { + c = new ArrayList(); + c.add("type the name of your client's domain here"); + c.add("(if 'origin_blacklist_use_simple_whitelist' is true)"); + c.add("g.lax1dude.net"); + c = this.get("origin_blacklist_simple_whitelist", c); + } + return c; + } + + public Collection getDisabledCommands() { + return this.get("disabled_commands", new ArrayList()); + } + + public Collection getICEServers() { + Collection ret = new ArrayList(); + + Collection c = this.get("voice_stun_servers", null); + if(c == null) { + c = new ArrayList(); + c.add("stun:stun.l.google.com:19302"); + c.add("stun:stun1.l.google.com:19302"); + c.add("stun:stun2.l.google.com:19302"); + c.add("stun:stun3.l.google.com:19302"); + c.add("stun:stun4.l.google.com:19302"); + c.add("stun:openrelay.metered.ca:80"); + c = this.get("voice_stun_servers", c); + } + + ret.addAll(c); + + Map turnServerList = this.get("voice_turn_servers", null); + if(turnServerList == null) { + turnServerList = new HashMap(); + HashMap n = new HashMap(); + n.put("url", "turn:openrelay.metered.ca:80"); + n.put("username", "openrelayproject"); + n.put("password", "openrelayproject"); + turnServerList.put("openrelay1", n); + + n = new HashMap(); + n.put("url", "turn:openrelay.metered.ca:443"); + n.put("username", "openrelayproject"); + n.put("password", "openrelayproject"); + turnServerList.put("openrelay2", n); + + n = new HashMap(); + n.put("url", "turn:openrelay.metered.ca:443?transport=tcp"); + n.put("username", "openrelayproject"); + n.put("password", "openrelayproject"); + turnServerList.put("openrelay3", n); + turnServerList = this.get("voice_turn_servers", turnServerList); + } + + for(Entry trn : turnServerList.entrySet()) { + Object o = trn.getValue(); + if(o instanceof Map) { + Map o2 = (Map) o; + ret.add("" + o2.get("url") + ";" + o2.get("username") + ";" + o2.get("password")); + } + } + + return ret; + } + + //@Override + public Collection getList(String path, Collection def) + { + return this.get(path, def); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/connection/CancelSendSignal.java b/eaglerbungee/src/main/java/net/md_5/bungee/connection/CancelSendSignal.java new file mode 100644 index 0000000..5afaac2 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/connection/CancelSendSignal.java @@ -0,0 +1,17 @@ +package net.md_5.bungee.connection; + +public class CancelSendSignal extends Error +{ + + @Override + public Throwable initCause(Throwable cause) + { + return this; + } + + @Override + public Throwable fillInStackTrace() + { + return this; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java b/eaglerbungee/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java new file mode 100644 index 0000000..e668f1c --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java @@ -0,0 +1,344 @@ +package net.md_5.bungee.connection; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; + +import java.beans.ConstructorProperties; +import java.io.DataInput; +import java.net.InetAddress; +import java.util.Objects; + +import net.md_5.bungee.*; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.event.ServerKickEvent; +import net.md_5.bungee.api.score.Objective; +import net.md_5.bungee.api.score.Position; +import net.md_5.bungee.api.score.Score; +import net.md_5.bungee.api.score.Scoreboard; +import net.md_5.bungee.api.score.Team; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.netty.PacketHandler; +import net.md_5.bungee.netty.PacketWrapper; +import net.md_5.bungee.protocol.packet.Packet0KeepAlive; +import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem; +import net.md_5.bungee.protocol.packet.PacketCEScoreboardObjective; +import net.md_5.bungee.protocol.packet.PacketCFScoreboardScore; +import net.md_5.bungee.protocol.packet.PacketD0DisplayScoreboard; +import net.md_5.bungee.protocol.packet.PacketD1Team; +import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; +import net.md_5.bungee.protocol.packet.PacketFFKick; + +public class DownstreamBridge extends PacketHandler +{ + + private final ProxyServer bungee; + private final UserConnection con; + private final ServerConnection server; + + @Override + public void exception(Throwable t) throws Exception + { + ServerInfo def = bungee.getServerInfo( con.getPendingConnection().getListener().getFallbackServer() ); + if ( server.getInfo() != def ) + { + server.setObsolete( true ); + con.connectNow( def ); + con.sendMessage( bungee.getTranslation( "server_went_down" ) ); + } else + { + con.disconnect( Util.exception( t ) ); + } + } + + @Override + public void disconnected(ChannelWrapper channel) throws Exception + { + // We lost connection to the server + server.getInfo().removePlayer( con ); + if ( bungee.getReconnectHandler() != null ) + { + bungee.getReconnectHandler().setServer( con ); + } + + if ( !server.isObsolete() ) + { + con.disconnect( bungee.getTranslation( "lost_connection" ) ); + } + } + + @Override + public void handle(PacketWrapper packet) throws Exception + { + if ( !server.isObsolete() ) + { + EntityMap.rewrite( packet.buf, con.getServerEntityId(), con.getClientEntityId() ); + con.sendPacket( packet ); + } + } + + @Override + public void handle(Packet0KeepAlive alive) throws Exception + { + con.setSentPingId( alive.getRandomId() ); + con.setSentPingTime( System.currentTimeMillis() ); + } + + @Override + public void handle(PacketC9PlayerListItem playerList) throws Exception + { + + if ( !con.getTabList().onListUpdate( playerList.getUsername(), playerList.isOnline(), playerList.getPing() ) ) + { + throw new CancelSendSignal(); + } + } + + @Override + public void handle(PacketCEScoreboardObjective objective) throws Exception + { + Scoreboard serverScoreboard = con.getServerSentScoreboard(); + switch ( objective.getAction() ) + { + case 0: + serverScoreboard.addObjective( new Objective( objective.getName(), objective.getText() ) ); + break; + case 1: + serverScoreboard.removeObjective( objective.getName() ); + break; + } + } + + @Override + public void handle(PacketCFScoreboardScore score) throws Exception + { + Scoreboard serverScoreboard = con.getServerSentScoreboard(); + switch ( score.getAction() ) + { + case 0: + Score s = new Score( score.getItemName(), score.getScoreName(), score.getValue() ); + serverScoreboard.removeScore( score.getItemName() ); + serverScoreboard.addScore( s ); + break; + case 1: + serverScoreboard.removeScore( score.getItemName() ); + break; + } + } + + @Override + public void handle(PacketD0DisplayScoreboard displayScoreboard) throws Exception + { + Scoreboard serverScoreboard = con.getServerSentScoreboard(); + serverScoreboard.setName( displayScoreboard.getName() ); + serverScoreboard.setPosition( Position.values()[displayScoreboard.getPosition()] ); + } + + @Override + public void handle(PacketD1Team team) throws Exception + { + Scoreboard serverScoreboard = con.getServerSentScoreboard(); + // Remove team and move on + if ( team.getMode() == 1 ) + { + serverScoreboard.removeTeam( team.getName() ); + return; + } + + // Create or get old team + Team t; + if ( team.getMode() == 0 ) + { + t = new Team( team.getName() ); + serverScoreboard.addTeam( t ); + } else + { + t = serverScoreboard.getTeam( team.getName() ); + } + + if ( t != null ) + { + if ( team.getMode() == 0 || team.getMode() == 2 ) + { + t.setDisplayName( team.getDisplayName() ); + t.setPrefix( team.getPrefix() ); + t.setSuffix( team.getSuffix() ); + t.setFriendlyFire( team.isFriendlyFire() ); + } + if ( team.getPlayers() != null ) + { + for ( String s : team.getPlayers() ) + { + if ( team.getMode() == 0 || team.getMode() == 3 ) + { + t.addPlayer( s ); + } else + { + t.removePlayer( s ); + } + } + } + } + } + + @Override + public void handle(final PacketFAPluginMessage pluginMessage) throws Exception { + final ByteArrayDataInput in = ByteStreams.newDataInput(pluginMessage.getData()); + final PluginMessageEvent event = new PluginMessageEvent(this.con.getServer(), this.con, pluginMessage.getTag(), pluginMessage.getData().clone()); + if (this.bungee.getPluginManager().callEvent(event).isCancelled()) { + throw new CancelSendSignal(); + } + if (pluginMessage.getTag().equals("MC|TPack") && this.con.getPendingConnection().getListener().getTexturePack() != null) { + throw new CancelSendSignal(); + } + if (pluginMessage.getTag().equals("BungeeCord")) { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + final String subChannel = in.readUTF(); + if (subChannel.equals("Forward")) { + final String target = in.readUTF(); + final String channel = in.readUTF(); + final short len = in.readShort(); + final byte[] data = new byte[len]; + in.readFully(data); + out.writeUTF(channel); + out.writeShort(data.length); + out.write(data); + final byte[] payload = out.toByteArray(); + out = null; + if (target.equals("ALL")) { + for (final ServerInfo server : this.bungee.getServers().values()) { + if (server != this.con.getServer().getInfo()) { + server.sendData("BungeeCord", payload); + } + } + } else { + final ServerInfo server2 = this.bungee.getServerInfo(target); + if (server2 != null) { + server2.sendData("BungeeCord", payload); + } + } + } + if (subChannel.equals("Connect")) { + final ServerInfo server3 = this.bungee.getServerInfo(in.readUTF()); + if (server3 != null) { + this.con.connect(server3); + } + } + if (subChannel.equals("IP")) { + out.writeUTF("IP"); + Object ob = this.con.getAttachment().get("remoteAddr"); + if(ob != null && (ob instanceof InetAddress)) { + out.writeUTF(((InetAddress)ob).getHostAddress()); + out.writeInt(this.con.getAddress().getPort()); + }else { + out.writeUTF(this.con.getAddress().getHostString()); + out.writeInt(this.con.getAddress().getPort()); + } + } + if (subChannel.equals("PlayerCount")) { + final String target = in.readUTF(); + out.writeUTF("PlayerCount"); + if (target.equals("ALL")) { + out.writeUTF("ALL"); + out.writeInt(this.bungee.getOnlineCount()); + } else { + final ServerInfo server4 = this.bungee.getServerInfo(target); + if (server4 != null) { + out.writeUTF(server4.getName()); + out.writeInt(server4.getPlayers().size()); + } + } + } + if (subChannel.equals("PlayerList")) { + final String target = in.readUTF(); + out.writeUTF("PlayerList"); + if (target.equals("ALL")) { + out.writeUTF("ALL"); + out.writeUTF(Util.csv(this.bungee.getPlayers())); + } else { + final ServerInfo server4 = this.bungee.getServerInfo(target); + if (server4 != null) { + out.writeUTF(server4.getName()); + out.writeUTF(Util.csv(server4.getPlayers())); + } + } + } + if (subChannel.equals("GetServers")) { + out.writeUTF("GetServers"); + out.writeUTF(Util.csv(this.bungee.getServers().keySet())); + } + if (subChannel.equals("Message")) { + final ProxiedPlayer target2 = this.bungee.getPlayer(in.readUTF()); + if (target2 != null) { + target2.sendMessage(in.readUTF()); + } + } + if (subChannel.equals("GetServer")) { + out.writeUTF("GetServer"); + out.writeUTF(this.server.getInfo().getName()); + } + if (subChannel.equals("EAG|GetDomain")) { + out.writeUTF("EAG|GetDomain"); + Object ob = this.con.getAttachment().get("origin"); + if(ob != null && (ob instanceof String)) { + out.writeBoolean(true); + out.writeUTF((String)ob); + }else { + out.writeBoolean(false); + out.writeUTF(""); + } + } + if (subChannel.equals("EAG|ConsoleCommand")) { + if(BungeeCord.getInstance().config.shouldAcceptBukkitConsoleCommandPacket()) { + String cmd = in.readUTF(); + bungee.getLogger().info("Connection [" + this.con.getName() + "] <-> [" + this.server.getInfo().getName() + "] executed bungee console command: " + cmd); + bungee.getPluginManager().dispatchCommand(bungee.getConsole(), cmd); + }else { + bungee.getLogger().info("Connection [" + this.con.getName() + "] <-> [" + this.server.getInfo().getName() + "] tried executing a bungee console command but \"accept_bukkit_console_command_packets\" is set to false in config.yml"); + } + } + if (out != null) { + final byte[] b = out.toByteArray(); + if (b.length != 0) { + this.con.getServer().sendData("BungeeCord", b); + } + } + } + } + + @Override + public void handle(PacketFFKick kick) throws Exception + { + ServerInfo def = bungee.getServerInfo( con.getPendingConnection().getListener().getFallbackServer() ); + if ( Objects.equals( server.getInfo(), def ) ) + { + def = null; + } + ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( con, kick.getMessage(), def, ServerKickEvent.State.CONNECTED ) ); + if ( event.isCancelled() && event.getCancelServer() != null ) + { + con.connectNow( event.getCancelServer() ); + } else + { + con.disconnect( bungee.getTranslation( "server_kick" ) + event.getKickReason() ); + } + server.setObsolete( true ); + throw new CancelSendSignal(); + } + + @Override + public String toString() + { + return "[" + con.getName() + "] <-> DownstreamBridge <-> [" + server.getInfo().getName() + "]"; + } + + @ConstructorProperties({ "bungee", "con", "server" }) + public DownstreamBridge(final ProxyServer bungee, final UserConnection con, final ServerConnection server) { + this.bungee = bungee; + this.con = con; + this.server = server; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/connection/InitialHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/connection/InitialHandler.java new file mode 100644 index 0000000..647c5c3 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/connection/InitialHandler.java @@ -0,0 +1,354 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.connection; + +import java.beans.ConstructorProperties; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; + +import com.google.common.base.Preconditions; + +import io.netty.channel.ChannelHandler; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.EncryptionUtil; +import net.md_5.bungee.PacketConstants; +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.Util; +import net.md_5.bungee.api.Callback; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.ServerPing; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.LoginEvent; +import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.api.event.ProxyPingEvent; +import net.md_5.bungee.eaglercraft.AuthHandler; +import net.md_5.bungee.eaglercraft.BanList; +import net.md_5.bungee.eaglercraft.BanList.BanCheck; +import net.md_5.bungee.eaglercraft.BanList.BanState; +import net.md_5.bungee.eaglercraft.WebSocketProxy; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.netty.CipherDecoder; +import net.md_5.bungee.netty.CipherEncoder; +import net.md_5.bungee.netty.HandlerBoss; +import net.md_5.bungee.netty.PacketDecoder; +import net.md_5.bungee.netty.PacketHandler; +import net.md_5.bungee.netty.PipelineUtils; +import net.md_5.bungee.protocol.Forge; +import net.md_5.bungee.protocol.packet.*; + +public class InitialHandler extends PacketHandler implements PendingConnection { + private final ProxyServer bungee; + private ChannelWrapper ch; + private final ListenerInfo listener; + private Packet1Login forgeLogin; + private Packet2Handshake handshake; + private PacketFDEncryptionRequest request; + private List loginMessages; + private List registerMessages; + private State thisState; + private SecretKey sharedKey; + private final Connection.Unsafe unsafe; + + @Override + public void connected(final ChannelWrapper channel) throws Exception { + this.ch = channel; + } + + @Override + public void exception(final Throwable t) throws Exception { + this.disconnect(ChatColor.RED + Util.exception(t)); + } + + @Override + public void handle(final PacketFAPluginMessage pluginMessage) throws Exception { + if (pluginMessage.getTag().equals("REGISTER")) { + this.registerMessages.add(pluginMessage); + } else { + this.loginMessages.add(pluginMessage); + } + } + + @Override + public void handle(final PacketFEPing ping) throws Exception { + ServerPing response = new ServerPing(this.bungee.getProtocolVersion(), this.bungee.getGameVersion(), this.listener.getMotd(), this.bungee.getOnlineCount(), this.listener.getMaxPlayers()); + response = this.bungee.getPluginManager().callEvent(new ProxyPingEvent(this, response)).getResponse(); + final String kickMessage = ChatColor.DARK_BLUE + "\u0000" + response.getProtocolVersion() + "\u0000" + response.getGameVersion() + "\u0000" + response.getMotd() + "\u0000" + response.getCurrentPlayers() + "\u0000" + + response.getMaxPlayers(); + this.disconnect(kickMessage); + } + + @Override + public void handle(final Packet1Login login) throws Exception { + Preconditions.checkState(this.thisState == State.LOGIN, (Object) "Not expecting FORGE LOGIN"); + Preconditions.checkState(this.forgeLogin == null, (Object) "Already received FORGE LOGIN"); + this.forgeLogin = login; + ((PacketDecoder) this.ch.getHandle().pipeline().get((Class) PacketDecoder.class)).setProtocol(Forge.getInstance()); + } + + @Override + public void handle(final Packet2Handshake handshake) throws Exception { + Preconditions.checkState(this.thisState == State.HANDSHAKE, (Object) "Not expecting HANDSHAKE"); + this.handshake = handshake; + this.bungee.getLogger().log(Level.INFO, "{0} has connected", this); + boolean skipEncryption = false; + if (handshake.getProcolVersion() == 69) { + skipEncryption = true; + this.handshake.swapProtocol((byte) 78); + }else if(handshake.getProcolVersion() > 78) { + this.disconnect("this server does not support microsoft accounts"); + return; + }else if(handshake.getProcolVersion() != 78) { + this.disconnect("minecraft 1.5.2 required for eaglercraft backdoor access"); + return; + } + String un = handshake.getUsername(); + if (un.length() < 3) { + this.disconnect("Username must be at least 3 characters"); + return; + } + if (un.length() > 16) { + this.disconnect("Cannot have username longer than 16 characters"); + return; + } + if(!un.equals(un.replaceAll("[^A-Za-z0-9\\-_]", "_").trim())) { + this.disconnect("Go fuck yourself"); + return; + } + if (BungeeCord.getInstance().tokenVerify.isEmpty()) { + String hostname = handshake.getHost(); + if (hostname.contains(":")) { + handshake.setHost(hostname.substring(0, hostname.indexOf(':'))); + } + } else { + handshake.setHost(BungeeCord.getInstance().tokenVerify); + handshake.setPort(0); + } + InetAddress sc; + synchronized(WebSocketProxy.localToRemote) { + sc = WebSocketProxy.localToRemote.get(this.ch.getHandle().remoteAddress()); + } + if(sc == null) { + this.bungee.getLogger().log(Level.WARNING, "player '" + un + "' doesn't have a websocket IP, remote address: " + this.ch.getHandle().remoteAddress().toString()); + }else { + BanCheck bc = BanList.checkIpBanned(sc); + if(bc.isBanned()) { + this.bungee.getLogger().log(Level.SEVERE, "Player '" + un + "' [" + sc.toString() + "] is banned by IP: " + bc.match + " (" + bc.string + ")"); + this.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: " + bc.string); + return; + }else { + this.bungee.getLogger().log(Level.INFO, "Player '" + un + "' [" + sc.toString() + "] has remote websocket IP: " + sc.getHostAddress()); + } + } + String dnm; + synchronized(WebSocketProxy.localToRemote) { + dnm = WebSocketProxy.origins.get(this.ch.getHandle().remoteAddress()); + } + if(dnm != null) { + if(dnm.equalsIgnoreCase("null")) { + this.bungee.getLogger().log(Level.INFO, "Player '" + un + "' [" + sc.toString() + "] is using an offline download"); + }else { + this.bungee.getLogger().log(Level.INFO, "Player '" + un + "' [" + sc.toString() + "] is using a client at: " + dnm); + } + } + BanCheck bc = BanList.checkBanned(un); + if(bc.isBanned()) { + switch(bc.reason) { + case USER_BANNED: + this.bungee.getLogger().log(Level.SEVERE, "Player '" + un + "' is banned by username, because '" + bc.string + "'"); + break; + case WILDCARD_BANNED: + this.bungee.getLogger().log(Level.SEVERE, "Player '" + un + "' is banned by wildcard: " + bc.match); + break; + case REGEX_BANNED: + this.bungee.getLogger().log(Level.SEVERE, "Player '" + un + "' is banned by regex: " + bc.match); + break; + default: + this.bungee.getLogger().log(Level.SEVERE, "Player '" + un + "' is banned: " + bc.string); + } + if(bc.reason == BanState.USER_BANNED || ((BungeeCord)bungee).config.shouldShowBanType()) { + this.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: " + bc.string); + }else { + this.disconnect("" + ChatColor.RED + "You are banned."); + } + return; + } + final int limit = BungeeCord.getInstance().config.getPlayerLimit(); + if (limit > 0 && this.bungee.getOnlineCount() > limit) { + this.disconnect(this.bungee.getTranslation("proxy_full")); + return; + } + if (!BungeeCord.getInstance().config.isOnlineMode() && this.bungee.getPlayer(un) != null) { + this.disconnect(this.bungee.getTranslation("already_connected")); + return; + } + this.unsafe().sendPacket(PacketConstants.I_AM_BUNGEE); + this.unsafe().sendPacket(PacketConstants.FORGE_MOD_REQUEST); + if(skipEncryption) { + InitialHandler.this.thisState = State.LOGIN; + handle((PacketCDClientStatus)null); + }else { + this.unsafe().sendPacket(this.request = EncryptionUtil.encryptRequest()); + this.thisState = State.ENCRYPT; + } + } + + @Override + public void handle(final PacketFCEncryptionResponse encryptResponse) throws Exception { + Preconditions.checkState(this.thisState == State.ENCRYPT, (Object) "Not expecting ENCRYPT"); + this.sharedKey = EncryptionUtil.getSecret(encryptResponse, this.request); + final Cipher decrypt = EncryptionUtil.getCipher(2, this.sharedKey); + this.ch.getHandle().pipeline().addBefore(PipelineUtils.PACKET_DECODE_HANDLER, PipelineUtils.DECRYPT_HANDLER, (ChannelHandler)new CipherDecoder(decrypt)); + this.finish(); + } + + private void finish() throws GeneralSecurityException { + final ProxiedPlayer old = this.bungee.getPlayer(this.handshake.getUsername()); + if (old != null) { + old.disconnect(this.bungee.getTranslation("already_connected")); + } + final Callback complete = new Callback() { + @Override + public void done(final LoginEvent result, final Throwable error) { + if (result.isCancelled()) { + InitialHandler.this.disconnect(result.getCancelReason()); + } + if (InitialHandler.this.ch.isClosed()) { + return; + } + InitialHandler.this.thisState = State.LOGIN; + InitialHandler.this.ch.getHandle().eventLoop().execute((Runnable)new Runnable() { + @Override + public void run() { + InitialHandler.this.unsafe().sendPacket(new PacketFCEncryptionResponse(new byte[0], new byte[0])); + try { + final Cipher encrypt = EncryptionUtil.getCipher(1, InitialHandler.this.sharedKey); + InitialHandler.this.ch.getHandle().pipeline().addBefore(PipelineUtils.DECRYPT_HANDLER, PipelineUtils.ENCRYPT_HANDLER, (ChannelHandler)new CipherEncoder(encrypt)); + } + catch (GeneralSecurityException ex) { + InitialHandler.this.disconnect("Cipher error: " + Util.exception(ex)); + } + } + }); + } + }; + this.bungee.getPluginManager().callEvent(new LoginEvent(this, complete)); + } + + @Override + public void handle(final PacketCDClientStatus clientStatus) throws Exception { + Preconditions.checkState(this.thisState == State.LOGIN, (Object) "Not expecting LOGIN"); + final UserConnection userCon = new UserConnection(this.bungee, this.ch, this.getName(), this); + InetAddress ins = WebSocketProxy.localToRemote.get(this.ch.getHandle().remoteAddress()); + if(ins != null) { + userCon.getAttachment().put("remoteAddr", ins); + } + String origin = WebSocketProxy.origins.get(this.ch.getHandle().remoteAddress()); + if(origin != null) { + userCon.getAttachment().put("origin", origin); + } + userCon.init(); + HandlerBoss handlerBoss = ((HandlerBoss) this.ch.getHandle().pipeline().get((Class) HandlerBoss.class)); + if (BungeeCord.getInstance().config.getAuthInfo().isEnabled()) { + handlerBoss.setHandler(new AuthHandler(this.bungee, userCon, handlerBoss)); + } else { + this.bungee.getPluginManager().callEvent(new PostLoginEvent(userCon)); + handlerBoss.setHandler(new UpstreamBridge(this.bungee, userCon)); + final ServerInfo server = this.bungee.getReconnectHandler().getServer(userCon); + userCon.connect(server, true); + } + this.thisState = State.FINISHED; + throw new CancelSendSignal(); + } + + @Override + public synchronized void disconnect(final String reason) { + if (!this.ch.isClosed()) { + this.unsafe().sendPacket(new PacketFFKick(reason)); + this.ch.close(); + } + } + + @Override + public String getName() { + return (this.handshake == null) ? null : this.handshake.getUsername(); + } + + @Override + public byte getVersion() { + return (byte) ((this.handshake == null) ? -1 : this.handshake.getProcolVersion()); + } + + @Override + public InetSocketAddress getVirtualHost() { + return (this.handshake == null) ? null : new InetSocketAddress(this.handshake.getHost(), this.handshake.getPort() & 0xFFFF); + } + + @Override + public InetSocketAddress getAddress() { + return (InetSocketAddress) this.ch.getHandle().remoteAddress(); + } + + @Override + public Connection.Unsafe unsafe() { + return this.unsafe; + } + + @Override + public String toString() { + return "[" + ((this.getName() != null) ? this.getName() : this.getAddress()) + "] <-> InitialHandler"; + } + + @ConstructorProperties({ "bungee", "listener" }) + public InitialHandler(final ProxyServer bungee, final ListenerInfo listener) { + this.loginMessages = new ArrayList(); + this.registerMessages = new ArrayList(); + this.thisState = State.HANDSHAKE; + this.unsafe = new Connection.Unsafe() { + @Override + public void sendPacket(final DefinedPacket packet) { + InitialHandler.this.ch.write(packet); + } + }; + this.bungee = bungee; + this.listener = listener; + } + + @Override + public ListenerInfo getListener() { + return this.listener; + } + + public Packet1Login getForgeLogin() { + return this.forgeLogin; + } + + public Packet2Handshake getHandshake() { + return this.handshake; + } + + public List getLoginMessages() { + return this.loginMessages; + } + + public List getRegisterMessages() { + return this.registerMessages; + } + + private enum State { + HANDSHAKE, ENCRYPT, LOGIN, FINISHED; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/connection/PingHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/connection/PingHandler.java new file mode 100644 index 0000000..0f60f45 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/connection/PingHandler.java @@ -0,0 +1,52 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.connection; + +import java.beans.ConstructorProperties; + +import net.md_5.bungee.api.Callback; +import net.md_5.bungee.api.ServerPing; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.netty.PacketHandler; +import net.md_5.bungee.protocol.packet.PacketFFKick; + +public class PingHandler extends PacketHandler { + private final ServerInfo target; + private final Callback callback; + private static final byte[] pingBuf; + + @Override + public void connected(final ChannelWrapper channel) throws Exception { + channel.write(PingHandler.pingBuf); + } + + @Override + public void exception(final Throwable t) throws Exception { + this.callback.done(null, t); + } + + @Override + public void handle(final PacketFFKick kick) throws Exception { + final String[] split = kick.getMessage().split("\u0000"); + final ServerPing ping = new ServerPing(Byte.parseByte(split[1]), split[2], split[3], Integer.parseInt(split[4]), Integer.parseInt(split[5])); + this.callback.done(ping, null); + } + + @Override + public String toString() { + return "[Ping Handler] -> " + this.target.getName(); + } + + @ConstructorProperties({ "target", "callback" }) + public PingHandler(final ServerInfo target, final Callback callback) { + this.target = target; + this.callback = callback; + } + + static { + pingBuf = new byte[] { -2, 1 }; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java b/eaglerbungee/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java new file mode 100644 index 0000000..d2e5afe --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java @@ -0,0 +1,148 @@ +package net.md_5.bungee.connection; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.EntityMap; +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.Util; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.event.ChatEvent; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.netty.PacketHandler; +import net.md_5.bungee.netty.PacketWrapper; +import net.md_5.bungee.protocol.packet.Packet0KeepAlive; +import net.md_5.bungee.protocol.packet.Packet3Chat; +import net.md_5.bungee.protocol.packet.PacketCBTabComplete; +import net.md_5.bungee.protocol.packet.PacketCCSettings; +import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; +import java.util.ArrayList; +import java.util.List; + +public class UpstreamBridge extends PacketHandler +{ + + private final ProxyServer bungee; + private final UserConnection con; + + public UpstreamBridge(ProxyServer bungee, UserConnection con) + { + this.bungee = bungee; + this.con = con; + + BungeeCord.getInstance().addConnection( con ); + con.getTabList().onConnect(); + con.unsafe().sendPacket( BungeeCord.getInstance().registerChannels() ); + } + + @Override + public void exception(Throwable t) throws Exception + { + con.disconnect( Util.exception( t ) ); + } + + @Override + public void disconnected(ChannelWrapper channel) throws Exception + { + // We lost connection to the client + PlayerDisconnectEvent event = new PlayerDisconnectEvent( con ); + bungee.getPluginManager().callEvent( event ); + con.getTabList().onDisconnect(); + BungeeCord.getInstance().removeConnection( con ); + + if ( con.getServer() != null ) + { + con.getServer().disconnect( "Quitting" ); + } + } + + @Override + public void handle(PacketWrapper packet) throws Exception + { + EntityMap.rewrite( packet.buf, con.getClientEntityId(), con.getServerEntityId() ); + if ( con.getServer() != null ) + { + con.getServer().getCh().write( packet ); + } + } + + @Override + public void handle(Packet0KeepAlive alive) throws Exception + { + if ( alive.getRandomId() == con.getSentPingId() ) + { + int newPing = (int) ( System.currentTimeMillis() - con.getSentPingTime() ); + con.getTabList().onPingChange( newPing ); + con.setPing( newPing ); + } + } + + @Override + public void handle(Packet3Chat chat) throws Exception + { + ChatEvent chatEvent = new ChatEvent( con, con.getServer(), chat.getMessage() ); + if ( !bungee.getPluginManager().callEvent( chatEvent ).isCancelled() ) + { + chat.setMessage( chatEvent.getMessage() ); + if ( !chatEvent.isCommand() || !bungee.getPluginManager().dispatchCommand( con, chat.getMessage().substring( 1 ) ) ) + { + con.getServer().unsafe().sendPacket( chat ); + } + } + throw new CancelSendSignal(); + } + + @Override + public void handle(PacketCBTabComplete tabComplete) throws Exception + { + if ( tabComplete.getCursor().startsWith( "/" ) ) + { + List results = new ArrayList<>(); + bungee.getPluginManager().dispatchCommand( con, tabComplete.getCursor().substring( 1 ), results ); + + if ( !results.isEmpty() ) + { + con.unsafe().sendPacket( new PacketCBTabComplete( results.toArray( new String[ results.size() ] ) ) ); + throw new CancelSendSignal(); + } + } + } + + @Override + public void handle(PacketCCSettings settings) throws Exception + { + con.setSettings( settings ); + } + + @Override + public void handle(PacketFAPluginMessage pluginMessage) throws Exception + { + if ( pluginMessage.getTag().equals( "BungeeCord" ) ) + { + throw new CancelSendSignal(); + } + // Hack around Forge race conditions + if ( pluginMessage.getTag().equals( "FML" ) && pluginMessage.getStream().readUnsignedByte() == 1 ) + { + throw new CancelSendSignal(); + } + + PluginMessageEvent event = new PluginMessageEvent( con, con.getServer(), pluginMessage.getTag(), pluginMessage.getData().clone() ); + if ( bungee.getPluginManager().callEvent( event ).isCancelled() ) + { + throw new CancelSendSignal(); + } + + // TODO: Unregister as well? + if ( pluginMessage.getTag().equals( "REGISTER" ) ) + { + con.getPendingConnection().getRegisterMessages().add( pluginMessage ); + } + } + + @Override + public String toString() + { + return "[" + con.getName() + "] -> UpstreamBridge"; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/AuthHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/AuthHandler.java new file mode 100644 index 0000000..f86f541 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/AuthHandler.java @@ -0,0 +1,206 @@ +package net.md_5.bungee.eaglercraft; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.ServerConnection; +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.Util; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.connection.CancelSendSignal; +import net.md_5.bungee.connection.UpstreamBridge; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.netty.HandlerBoss; +import net.md_5.bungee.netty.PacketHandler; +import net.md_5.bungee.protocol.packet.Packet1Login; +import net.md_5.bungee.protocol.packet.Packet9Respawn; +import net.md_5.bungee.protocol.packet.Packet0DPositionAndLook; +import net.md_5.bungee.protocol.packet.Packet3Chat; +import net.md_5.bungee.protocol.packet.Packet0KeepAlive; +import net.md_5.bungee.protocol.packet.PacketCCSettings; +import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; +import net.md_5.bungee.protocol.packet.PacketFEPing; + +public class AuthHandler extends PacketHandler { + private static final AuthSystem authSystem = BungeeCord.getInstance().authSystem; + + private final ProxyServer bungee; + private final UserConnection con; + private final HandlerBoss handlerBoss; + private final String username; + + private static final Collection openHandlers = new LinkedList(); + private boolean loggedIn = false; + private long startTime; + + public AuthHandler(final ProxyServer bungee, final UserConnection con, final HandlerBoss handlerBoss) { + this.bungee = bungee; + this.con = con; + this.handlerBoss = handlerBoss; + this.username = this.con.getName(); + this.startTime = System.currentTimeMillis(); + + synchronized(openHandlers) { + openHandlers.add(this); + } + + this.con.unsafe().sendPacket(new Packet1Login(0, "END", (byte) 2, 1, (byte) 0, (byte) 0, + (byte) this.con.getPendingConnection().getListener().getTabListSize())); + this.con.unsafe().sendPacket(new Packet9Respawn(1, (byte) 0, (byte) 2, (short) 255, "END")); + this.con.unsafe().sendPacket(new Packet0DPositionAndLook(0, 0, 0, 0, 0f, 0f, true)); + + this.con.sendMessages(authSystem.joinMessages); + + if (authSystem.isRegistered(this.username)) { + this.con.sendMessage("\u00A7cPlease login to continue! /login "); + } else { + this.con.sendMessage("\u00A7cPlease register to continue! /register "); + } + } + + @Override + public void exception(final Throwable t) throws Exception { + this.con.disconnect(Util.exception(t)); + } + + @Override + public void disconnected(final ChannelWrapper channel) { + this.loggedIn = true; + } + + @Override + public void handle(final Packet0KeepAlive alive) throws Exception { + if (alive.getRandomId() == this.con.getSentPingId()) { + final int newPing = (int) (System.currentTimeMillis() - this.con.getSentPingTime()); + this.con.setPing(newPing); + } + } + + private List pms = new ArrayList<>(); + + @Override + public void handle(final PacketFAPluginMessage p) throws Exception { + pms.add(p); + throw new CancelSendSignal(); + } + + @Override + public void handle(final PacketFEPing p) throws Exception { + this.con.getPendingConnection().handle(p); + } + + @Override + public void handle(final Packet3Chat chat) throws Exception { + String message = chat.getMessage(); + if (message.startsWith("/")) { + String[] args = message.substring(1).trim().split(" "); + switch (args[0]) { + case "login": + case "l": + if (args.length == 1) { + this.con.sendMessage("\u00A7cYou must specify a password to login! /login "); + } else if (!authSystem.isRegistered(this.username)) { + this.con.sendMessage("\u00A7cThis username is not registered on this server!"); + } else if (authSystem.login(this.username, args[1])) { + this.con.sendMessage("\u00A7cLogging in..."); + this.onLogin(); + } else { + this.con.sendMessage("\u00A7cThat password is invalid!"); + } + break; + case "register": + case "reg": + if(BungeeCord.getInstance().config.getAuthInfo().isRegisterEnabled()) { + if (args.length == 1 || args.length == 2) { + this.con.sendMessage("\u00A7cUsage: /" + args[0].toLowerCase() + " "); + } else if (!args[1].equals(args[2])) { + this.con.sendMessage("\u00A7cThose passwords do not match!"); + } else if (authSystem.isRegistered(this.username)) { + this.con.sendMessage("\u00A7cThis username is already registered!"); + } else if (authSystem.register(this.username, args[1], + this.con.getAddress().getAddress().getHostAddress())) { + this.con.sendMessage("\u00A7cSuccessfully registered and logging in..."); + this.onLogin(); + } else { + this.con.sendMessage("\u00A7cUnable to register..."); + } + }else { + this.con.disconnect("Registration is not enabled!"); + } + break; + case "changepassword": + case "changepasswd": + case "changepwd": + case "changepass": + if (args.length == 1 || args.length == 2) { + this.con.sendMessage("\u00A7cUsage: /" + args[0].toLowerCase() + " "); + } else if (authSystem.login(this.username, args[1])) { + if (authSystem.changePass(this.username, args[2])) { + this.con.sendMessage("\u00A7cPassword changed successfully!"); + } else { + this.con.sendMessage("\u00A7cUnable to change your password..."); + } + } else { + this.con.sendMessage("\u00A7cThe old password specified is incorrect!"); + } + break; + default: + } + } + } + + private void onLogin() throws Exception { + this.loggedIn = true; + this.bungee.getPluginManager().callEvent(new PostLoginEvent(this.con)); + UpstreamBridge ub = new UpstreamBridge(this.bungee, this.con); + handlerBoss.setHandler(ub); + final ServerInfo server = this.bungee.getReconnectHandler().getServer(this.con); + this.con.setServer(new ServerConnection(null, null)); + this.con.connect(server, true); + for (PacketFAPluginMessage pm : pms) { + try { + ub.handle(pm); + this.con.getPendingConnection().getLoginMessages().add(pm); + } catch (CancelSendSignal e) { + // don't forward to server + } + } + pms.clear(); + } + + @Override + public void handle(final PacketCCSettings settings) throws Exception { + this.con.setSettings(settings); + } + + @Override + public String toString() { + return "[" + this.con.getName() + "] -> AuthHandler"; + } + + public static void closeInactive(int timeout) { + synchronized(openHandlers) { + long millis = System.currentTimeMillis(); + timeout *= 1000; + Iterator handlers = openHandlers.iterator(); + while(handlers.hasNext()) { + AuthHandler h = handlers.next(); + if(!h.loggedIn) { + if(millis - h.startTime > timeout) { + h.con.disconnect("You did not login in time you eagler!"); + handlers.remove(); + } + }else { + handlers.remove(); + } + } + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/AuthSystem.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/AuthSystem.java new file mode 100644 index 0000000..de2fc01 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/AuthSystem.java @@ -0,0 +1,206 @@ +package net.md_5.bungee.eaglercraft; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.config.AuthServiceInfo; + +import java.io.File; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AuthSystem { + private final String authFileName; + private final int ipLimit; + public final String[] joinMessages; + + public AuthSystem(AuthServiceInfo authInfo) { + this.authFileName = authInfo.getAuthfile(); + this.ipLimit = authInfo.getIpLimit(); + List listJoinMessages = authInfo.getJoinMessages(); + String[] arrayJoinMessages = new String[listJoinMessages.size()]; + for (int i = 0; i < listJoinMessages.size(); i++) { + arrayJoinMessages[i] = ChatColor.translateAlternateColorCodes('&', listJoinMessages.get(i)); + } + this.joinMessages = arrayJoinMessages; + + this.readDatabase(); + } + + private static class AuthData { + public String salt; + public String hash; + public String ip; + public long timestamp; + + public AuthData(String salt, String hash, String ip, long timestamp) { + this.salt = salt; + this.hash = hash; + this.ip = ip; + this.timestamp = timestamp; + } + } + + private final Map database = new HashMap<>(); + + public boolean register(String username, String password, String ip) { + username = username.toLowerCase(); + synchronized (database) { + AuthData authData = database.get(username); + if (authData != null) + return false; + if (isIpAtTheLimit(ip)) + return false; + String salt = createSalt(16); + String hash = getSaltedHash(password, salt); + database.put(username, new AuthData(salt, hash, ip, System.currentTimeMillis())); + writeDatabase(); + return true; + } + } + + public boolean isRegistered(String username) { + username = username.toLowerCase(); + synchronized (database) { + return database.containsKey(username); + } + } + + public boolean changePass(String username, String password) { + username = username.toLowerCase(); + synchronized (database) { + AuthData authData = database.get(username); + authData.salt = createSalt(16); + authData.hash = getSaltedHash(password, authData.salt); + writeDatabase(); + return true; + } + } + + public boolean login(String username, String password) { + username = username.toLowerCase(); + synchronized (database) { + AuthData authData = database.get(username); + if (authData == null) + return false; + return authData.hash.equals(getSaltedHash(password, authData.salt)); + } + } + + private boolean isIpAtTheLimit(String ip) { + synchronized (database) { + if (this.ipLimit <= 0) + return false; + int num = 0; + for (AuthData authData : database.values()) { + if (authData.ip.equals(ip)) + num++; + if (num >= this.ipLimit) { + return true; + } + } + return false; + } + } + + // only use once, on load + public void readDatabase() { + synchronized (database) { + try { + File authFile = new File(this.authFileName); + if (!authFile.exists()) + authFile.createNewFile(); + + database.clear(); + + String[] lines = new String(Files.readAllBytes(authFile.toPath())).trim().split("\n"); + if (lines.length == 1 && lines[0].isEmpty()) + return; + boolean alreadyLogged = false; + for (String line : lines) { + String[] pieces = line.split(":"); + if (!pieces[1].startsWith("$SHA$")) { + if (!alreadyLogged) { + alreadyLogged = true; + BungeeCord.getInstance().getLogger().warning( + "One or more entries in the auth file are hashed in an unsupported format! (not SHA-256!)"); + } + // continue; + } + String[] saltHash = pieces[1].substring(pieces[1].substring(1).indexOf('$') + 2).split("\\$"); + database.put(pieces[0], + new AuthData(saltHash[0], saltHash[1], pieces[2], Long.parseLong(pieces[3]))); + } + + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private void writeDatabase() { + synchronized (database) { + StringBuilder out = new StringBuilder(); + + for (String username : database.keySet()) { + AuthData entry = database.get(username); + out.append(username); + out.append(":$SHA$"); + out.append(entry.salt); + out.append("$"); + out.append(entry.hash); + out.append(":"); + out.append(entry.ip); + out.append(":"); + out.append(entry.timestamp); + out.append("\n"); + } + + try { + Files.write(Paths.get(this.authFileName), out.toString().getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + // hashing used is based on hashing from AuthMe + + private static final SecureRandom rnd = new SecureRandom(); + + private static String getSHA256(String message) { + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + sha256.reset(); + sha256.update(message.getBytes()); + byte[] digest = sha256.digest(); + return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + } catch (NoSuchAlgorithmException e) { + return ""; + } + } + + private static String getSaltedHash(String message, String salt) { + return getSHA256(getSHA256(message) + salt); + } + + private static String createSalt(int length) { + try { + byte[] msg = new byte[40]; + rnd.nextBytes(msg); + MessageDigest sha1 = MessageDigest.getInstance("SHA1"); + sha1.reset(); + byte[] digest = sha1.digest(msg); + return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)).substring(0, length); + } catch (NoSuchAlgorithmException e) { + return ""; + } + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/BanList.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/BanList.java new file mode 100644 index 0000000..28b743d --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/BanList.java @@ -0,0 +1,872 @@ +package net.md_5.bungee.eaglercraft; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.eaglercraft.sun.net.util.IPAddressUtil; + +public class BanList { + + private static final Object banListMutex = new Object(); + + public static enum BanState { + NOT_BANNED, USER_BANNED, IP_BANNED, WILDCARD_BANNED, REGEX_BANNED; + } + + public static class BanCheck { + public final BanState reason; + public final String match; + public final String string; + private BanCheck(BanState reason, String match, String string) { + this.reason = reason; + this.match = match; + this.string = string; + } + public boolean isBanned() { + return reason != BanState.NOT_BANNED; + } + } + + private static class RegexBan { + public final String string; + public final Pattern compiled; + private RegexBan(String string, Pattern compiled) { + this.string = string; + this.compiled = compiled; + } + public String toString() { + return string; + } + public int hashCode() { + return string.hashCode(); + } + } + + public static interface IPBan { + + boolean checkBan(InetAddress addr); + InetAddress getBaseAddress(); + boolean hasNetMask(); + + } + + private static class IPBan4 implements IPBan { + + private final int addr; + private final InetAddress addrI; + private final int mask; + private final String string; + + protected IPBan4(Inet4Address addr, String s, int mask) { + if(mask >= 32) { + this.mask = 0xFFFFFFFF; + }else { + this.mask = ~((1 << (32 - mask)) - 1); + } + this.string = s; + byte[] bits = addr.getAddress(); + this.addr = this.mask & ((bits[0] << 24) | (bits[1] << 16) | (bits[2] << 8) | (bits[3] & 0xFF)); + this.addrI = addr; + } + + @Override + public boolean checkBan(InetAddress addr4) { + if(addr4 instanceof Inet4Address) { + Inet4Address a = (Inet4Address)addr4; + byte[] bits = a.getAddress(); + int addrBits = ((bits[0] << 24) | (bits[1] << 16) | (bits[2] << 8) | (bits[3] & 0xFF)); + return (mask & addrBits) == addr; + }else { + return false; + } + } + + @Override + public InetAddress getBaseAddress() { + return addrI; + } + + @Override + public String toString() { + return string; + } + + @Override + public int hashCode() { + return string.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o != null && o instanceof IPBan4 && ((IPBan4)o).addr == addr && ((IPBan4)o).mask == mask; + } + + @Override + public boolean hasNetMask() { + return mask != 0xFFFFFFFF; + } + + } + + private static class IPBan6 implements IPBan { + + private static final BigInteger mask128 = new BigInteger(1, new byte[] { + (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, + (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, + (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF, + (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF + }); + + private final BigInteger addr; + private final InetAddress addrI; + private final BigInteger mask; + private final String string; + + protected IPBan6(Inet6Address addr, String s, int mask) { + this.mask = BigInteger.valueOf(1l).shiftLeft(128 - mask).subtract(BigInteger.valueOf(1l)).xor(mask128); + this.string = s.toLowerCase(); + this.addr = new BigInteger(1, addr.getAddress()).and(this.mask); + this.addrI = addr; + } + + @Override + public boolean checkBan(InetAddress addr6) { + if(addr6 instanceof Inet6Address) { + Inet6Address a = (Inet6Address)addr6; + BigInteger addrBits = new BigInteger(1, a.getAddress()).and(this.mask); + return addr.equals(addrBits); + }else { + return false; + } + } + + @Override + public InetAddress getBaseAddress() { + return addrI; + } + + @Override + public String toString() { + return string; + } + + @Override + public int hashCode() { + return string.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o != null && o instanceof IPBan6 && ((IPBan6)o).addr.equals(addr) && ((IPBan6)o).mask.equals(mask); + } + + @Override + public boolean hasNetMask() { + return !mask.equals(mask128); + } + + } + + public static final File bansFile = new File("bans.txt"); + + public static final String banChatMessagePrefix = ChatColor.GOLD + "[BanList] "; + + public static final Map userBans = new HashMap(); + public static final Set ipBans = new HashSet(); + public static final Set wildcardBans = new HashSet(); + public static final Set regexBans = new HashSet(); + private static List currentBanList = null; + + public static final List blockedBans = new ArrayList(); + + static { + try { + blockedBans.add(constructIpBan("127.0.0.0/8")); + }catch(UnknownHostException e) { + BungeeCord.getInstance().getLogger().severe("Error: could not whitelist '127.0.0.0/8'"); + e.printStackTrace(); + } + try { + blockedBans.add(constructIpBan("10.0.0.0/8")); + }catch(UnknownHostException e) { + BungeeCord.getInstance().getLogger().severe("Error: could not whitelist '10.0.0.0/8'"); + e.printStackTrace(); + } + try { + blockedBans.add(constructIpBan("172.24.0.0/14")); + }catch(UnknownHostException e) { + BungeeCord.getInstance().getLogger().severe("Error: could not whitelist '172.24.0.0/14'"); + e.printStackTrace(); + } + try { + blockedBans.add(constructIpBan("192.168.0.0/16")); + }catch(UnknownHostException e) { + BungeeCord.getInstance().getLogger().severe("Error: could not whitelist '192.168.0.0/16'"); + e.printStackTrace(); + } + try { + blockedBans.add(constructIpBan("::1/128")); + }catch(UnknownHostException e) { + BungeeCord.getInstance().getLogger().severe("Error: could not whitelist '::1/128'"); + e.printStackTrace(); + } + } + + public static boolean isBlockedBan(InetAddress addr) { + for(IPBan b : BanList.blockedBans) { + if(b.checkBan(addr)) { + return true; + } + } + return false; + } + + private static long lastListTest = 0l; + private static long lastListLoad = 0l; + private static boolean fileIsBroken = false; + + public static BanCheck checkIpBanned(InetAddress addr) { + synchronized(banListMutex) { + for(IPBan b : ipBans) { + if(b.checkBan(addr)) { + return new BanCheck(BanState.IP_BANNED, b.toString(), b.hasNetMask() ? "Banned by Netmask" : "Banned by IP"); + } + } + } + return new BanCheck(BanState.NOT_BANNED, "none", "not banned"); + } + + public static BanCheck checkBanned(String player) { + synchronized(banListMutex) { + player = player.trim().toLowerCase(); + String r = userBans.get(player); + if(r != null) { + if(r.length() <= 0) { + r = "The ban hammer has spoken"; + } + return new BanCheck(BanState.USER_BANNED, player, r); + } + for(String ss : wildcardBans) { + String s = ss; + boolean startStar = s.startsWith("*"); + if(startStar) { + s = s.substring(1); + } + boolean endStar = s.endsWith("*"); + if(endStar) { + s = s.substring(0, s.length() - 1); + } + if(startStar && endStar) { + if(player.contains(s)) { + return new BanCheck(BanState.WILDCARD_BANNED, ss, "You've been banned via wildcard"); + } + }else if(endStar) { + if(player.startsWith(s)) { + return new BanCheck(BanState.WILDCARD_BANNED, ss, "You've been banned via wildcard"); + } + }else if(startStar) { + if(player.endsWith(s)) { + return new BanCheck(BanState.WILDCARD_BANNED, ss, "You've been banned via wildcard"); + } + }else { + if(player.equals(s)) { + return new BanCheck(BanState.WILDCARD_BANNED, ss, "You've been banned via wildcard"); + } + } + } + for(RegexBan p : regexBans) { + if(p.compiled.matcher(player).matches()) { + return new BanCheck(BanState.REGEX_BANNED, p.string, "You've been banned via regex"); + } + } + } + return new BanCheck(BanState.NOT_BANNED, "none", "not banned"); + } + + private static void saveCurrentBanListLines() { + try { + PrintWriter pf = new PrintWriter(new FileWriter(bansFile)); + for(String s : currentBanList) { + pf.println(s); + } + pf.close(); + lastListLoad = lastListTest = System.currentTimeMillis(); + }catch(Throwable t) { + BungeeCord.getInstance().getLogger().severe("ERROR: the ban list could not be saved to file '" + bansFile.getName() + "', please fix this or you will lose all your bans next time this server restarts"); + t.printStackTrace(); + } + } + + private static boolean addEntryToFile(BanState b, String s) { + if(b == null || b == BanState.NOT_BANNED) { + return false; + } + String wantedHeader = b == BanState.USER_BANNED ? "[Usernames]" : (b == BanState.WILDCARD_BANNED ? "[Wildcards]" : (b == BanState.REGEX_BANNED ? "[Regex]" : (b == BanState.IP_BANNED ? "[IPs]" : "shit"))); + int lastFullPart = -1; + boolean isFilePart = false; + boolean isPartStart = false; + for(int i = 0, l = currentBanList.size(); i < l; ++i) { + String ss = currentBanList.get(i).trim(); + if(ss.length() <= 0) { + continue; + } + if(ss.startsWith("#")) { + continue; + } + if(ss.equalsIgnoreCase(wantedHeader)) { + isFilePart = true; + isPartStart = true; + lastFullPart = i; + }else if(ss.indexOf('[') != -1) { + if(isFilePart) { + break; + } + }else { + if(isFilePart) { + lastFullPart = i; + isPartStart = false; + } + } + } + if(lastFullPart != -1) { + if(isPartStart) { + lastFullPart += 1; + currentBanList.add(lastFullPart, ""); + } + lastFullPart += 1; + currentBanList.add(lastFullPart, s); + lastFullPart += 1; + if(currentBanList.size() > lastFullPart && currentBanList.get(lastFullPart).trim().length() > 0) { + currentBanList.add(lastFullPart, ""); + } + }else { + if(currentBanList.size() > 0 && currentBanList.get(currentBanList.size() - 1).trim().length() > 0) { + currentBanList.add(""); + } + currentBanList.add(wantedHeader); + currentBanList.add(""); + currentBanList.add(s); + currentBanList.add(""); + } + saveCurrentBanListLines(); + return true; + } + + private static boolean removeEntryFromFile(BanState b, String s, boolean ignoreCase) { + if(b == null || b == BanState.NOT_BANNED) { + return false; + } + String wantedHeader = b == BanState.USER_BANNED ? "[Usernames]" : (b == BanState.WILDCARD_BANNED ? "[Wildcards]" : (b == BanState.REGEX_BANNED ? "[Regex]" : (b == BanState.IP_BANNED ? "[IPs]" : "shit"))); + Iterator lns = currentBanList.iterator(); + boolean isFilePart = false; + boolean wasRemoved = false; + while(lns.hasNext()) { + String ss = lns.next().trim(); + if(ss.length() <= 0) { + continue; + } + if(ss.startsWith("#")) { + continue; + } + if(ss.equalsIgnoreCase(wantedHeader)) { + isFilePart = true; + }else if(ss.indexOf('[') != -1) { + isFilePart = false; + }else { + if(b == BanState.USER_BANNED && ss.contains(":")) { + ss = ss.substring(0, ss.indexOf(':')).trim(); + } + if(isFilePart && (ignoreCase ? ss.equalsIgnoreCase(s) : ss.equals(s))) { + lns.remove(); + wasRemoved = true; + } + } + } + if(wasRemoved) { + saveCurrentBanListLines(); + } + return wasRemoved; + } + + public static boolean unban(String player) { + synchronized(banListMutex) { + String s = player.trim().toLowerCase(); + if(userBans.remove(s) != null) { + removeEntryFromFile(BanState.USER_BANNED, player, true); + return true; + }else { + return false; + } + } + } + + public static boolean ban(String player, String reason) { + synchronized(banListMutex) { + player = player.trim().toLowerCase(); + if(userBans.put(player, reason) == null) { + addEntryToFile(BanState.USER_BANNED, player + (reason == null || reason.length() <= 0 ? "" : ": " + reason)); + return true; + }else { + return false; + } + } + } + + public static boolean banWildcard(String wc) throws PatternSyntaxException { + synchronized(banListMutex) { + wc = wc.trim().toLowerCase(); + boolean b = wc.contains("*"); + if(!b || (b && !wc.startsWith("*") && !wc.endsWith("*"))) { + throw new PatternSyntaxException("Wildcard can only begin and/or end with *", wc, 0); + } + if(wildcardBans.add(wc)) { + addEntryToFile(BanState.WILDCARD_BANNED, wc); + return true; + }else { + return false; + } + } + } + + public static boolean unbanWildcard(String wc) { + synchronized(banListMutex) { + wc = wc.trim().toLowerCase(); + if(wildcardBans.remove(wc)) { + removeEntryFromFile(BanState.WILDCARD_BANNED, wc, true); + return true; + }else { + return false; + } + } + } + + public static boolean banRegex(String regex) throws PatternSyntaxException { + synchronized(banListMutex) { + regex = regex.trim(); + Pattern p = Pattern.compile(regex); + if(regexBans.add(new RegexBan(regex, p))) { + addEntryToFile(BanState.REGEX_BANNED, regex); + return true; + }else { + return false; + } + } + } + + public static boolean unbanRegex(String regex) { + synchronized(banListMutex) { + regex = regex.trim(); + Iterator banz = regexBans.iterator(); + while(banz.hasNext()) { + if(banz.next().string.equals(regex)) { + banz.remove(); + removeEntryFromFile(BanState.REGEX_BANNED, regex, false); + return true; + } + } + return false; + } + } + + public static IPBan constructIpBan(String ip) throws UnknownHostException { + synchronized(banListMutex) { + ip = ip.trim(); + String s = ip; + int subnet = -1; + int i = s.indexOf('/'); + if(i != -1) { + String s2 = s.substring(i + 1); + s = s.substring(0, i); + try { + subnet = Integer.parseInt(s2); + }catch(Throwable t) { + throw new UnknownHostException("Invalid netmask: '" + s + "'"); + } + } + + if(!IPAddressUtil.isIPv4LiteralAddress(s) && !IPAddressUtil.isIPv6LiteralAddress(s)) { + throw new UnknownHostException("Invalid address: '" + s + "'"); + } + + InetAddress aa = InetAddress.getByName(s); + if(aa instanceof Inet4Address) { + if(subnet > 32 || subnet < -1) { + throw new UnknownHostException("IPv4 netmask '" + subnet + "' is invalid"); + } + if(subnet == -1) { + subnet = 32; + } + return new IPBan4((Inet4Address)aa, ip, subnet); + }else if(aa instanceof Inet6Address) { + if(subnet > 128 || subnet < -1) { + throw new UnknownHostException("IPv6 netmask '" + subnet + "' is invalid"); + } + if(subnet == -1) { + subnet = 128; + } + return new IPBan6((Inet6Address)aa, ip, subnet); + }else { + throw new UnknownHostException("Only ipv4 and ipv6 addresses allowed in Eaglercraft"); + } + } + } + + public static boolean banIP(String ip) throws UnknownHostException { + synchronized(banListMutex) { + ip = ip.trim(); + IPBan b = constructIpBan(ip); + if(b != null) { + if(ipBans.add(b)) { + addEntryToFile(BanState.IP_BANNED, ip); + return true; + } + } + return false; + } + } + + public static boolean unbanIP(String ip) throws UnknownHostException { + synchronized(banListMutex) { + ip = ip.trim(); + IPBan b = constructIpBan(ip); + if(b != null) { + Iterator banz = ipBans.iterator(); + while(banz.hasNext()) { + IPBan bb = banz.next(); + if(bb.equals(b)) { + banz.remove(); + removeEntryFromFile(BanState.IP_BANNED, bb.toString(), true); + return true; + } + } + } + return false; + } + } + + private static final int MAX_CHAT_LENGTH = 118; + + public static String listAllBans() { + synchronized(banListMutex) { + String ret = ""; + for(String s : userBans.keySet()) { + if(ret.length() > 0) { + ret += ", "; + } + ret += s; + } + return ret.length() > 0 ? ret : "(none)"; + } + } + + public static String listAllWildcardBans() { + synchronized(banListMutex) { + String ret = ""; + for(String s : wildcardBans) { + if(ret.length() > 0) { + ret += ", "; + } + ret += s; + } + return ret.length() > 0 ? ret : "(none)"; + } + } + + public static String listAllRegexBans() { + synchronized(banListMutex) { + String ret = ""; + for(RegexBan s : regexBans) { + if(ret.length() > 0) { + ret += " | "; + } + ret += s.string; + } + return ret.length() > 0 ? ret : "(none)"; + } + } + + public static String listAllIPBans(boolean v6, boolean netmask) { + synchronized(banListMutex) { + String ret = ""; + for(IPBan b : ipBans) { + if(v6) { + if(b instanceof IPBan6) { + IPBan6 b2 = (IPBan6)b; + if(netmask == b2.hasNetMask()) { + if(ret.length() > 0) { + ret += ", "; + } + ret += b2.string; + } + } + }else { + if(b instanceof IPBan4) { + IPBan4 b2 = (IPBan4)b; + if(netmask == b2.hasNetMask()) { + if(ret.length() > 0) { + ret += ", "; + } + ret += b2.string; + } + } + } + } + if(ret.length() <= 0) { + ret = "(none)"; + } + return ret; + } + } + + public static void maybeReloadBans(CommandSender cs) { + synchronized(banListMutex) { + long st = System.currentTimeMillis(); + if(cs == null && st - lastListTest < 1000l) { + return; + } + lastListTest = st; + boolean ex = bansFile.exists(); + if(!fileIsBroken && !ex) { + try { + PrintWriter p = new PrintWriter(new FileWriter(bansFile)); + p.println(); + p.println("#"); + p.println("# This file allows you to configure bans for eaglercraftbungee"); + p.println("# When it is saved, eaglercraft should reload it automatically"); + p.println("# (check the console though to be safe)"); + p.println("#"); + p.println("# For a [Usernames] ban, just add the player's name. Use a colon ':' to put in a ban reason"); + p.println("# For a [IPs] ban, just add the player's IP, or a subnet like 69.69.0.0/16 to ban all IPs beginning with 69.69.*"); + p.println("# For a [Wildcards] ban, type a string and prefix and/or suffix it with * to define the wildcard"); + p.println("# For a [Regex] ban, type a valid regular expression in the java.util.regex format"); + p.println("#"); + p.println("# All bans are case-insensitive, USERNAMES ARE CONVERTED TO LOWERCASE BEFORE BEING MATCHED VIA REGEX"); + p.println("# Java regex syntax: https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html"); + p.println("#"); + p.println(); + p.println("# set this to to true to use \"/ban\" to ban on bungee instead of \"/eag-ban\""); + p.println("# (most likely needs a restart to take effect)"); + p.println("replace-bukkit=false"); + p.println(); + p.println(); + p.println("[Usernames]"); + p.println(); + p.println("# ban_test1: The ban hammer has spoken!"); + p.println("# ban_test2: custom ban message here"); + p.println("# ban_test3"); + p.println(); + p.println("# (remove the '#' before each line to enable)"); + p.println(); + p.println("[IPs]"); + p.println(); + p.println("# WARNING: if you're using nginx, banning any player's IP is gonna ban ALL PLAYERS on your server"); + p.println("# For this reason, the ban IP command doesn't ban 127.0.0.1 or any other 'private' range IPs"); + p.println(); + p.println("# 101.202.69.11"); + p.println("# 123.21.43.0/24"); + p.println("# 2601:1062:69:418:BEEF::10"); + p.println("# 2601:6090:420::/48"); + p.println(); + p.println(); + p.println("[Wildcards]"); + p.println(); + p.println("# *fuck*"); + p.println("# shi*"); + p.println(); + p.println(); + p.println("[Regex]"); + p.println(); + p.println("# you.+are.(a|the).+bitch"); + p.println(); + p.println(); + p.println("# end of file"); + p.println(); + p.close(); + BungeeCord.getInstance().getLogger().info("Wrote a new bans.txt to: " + bansFile.getAbsolutePath()); + lastListLoad = 0l; + }catch(Throwable t) { + fileIsBroken = true; + if(cs != null) { + cs.sendMessage(banChatMessagePrefix + ChatColor.RED + "Could not create blank 'bans.txt' list file"); + cs.sendMessage(banChatMessagePrefix + ChatColor.RED + "(Reason: " + t.toString() + ")"); + } + BungeeCord.getInstance().getLogger().severe("Could not create blank 'bans.txt' list file"); + BungeeCord.getInstance().getLogger().severe("(Reason: " + t.toString() + ")"); + t.printStackTrace(); + } + return; + } + if(fileIsBroken && ex) { + fileIsBroken = false; + lastListLoad = 0l; + } + if(fileIsBroken) { + return; + } + long lastEdit = bansFile.lastModified(); + if(cs != null || lastEdit - lastListLoad > 400l) { + try { + BufferedReader r = new BufferedReader(new FileReader(bansFile)); + currentBanList = new LinkedList(); + String s; + while((s = r.readLine()) != null) { + currentBanList.add(s); + } + r.close(); + lastListLoad = lastEdit; + BungeeCord.getInstance().getLogger().info("Server bans.txt changed, it will be reloaded automatically"); + if(cs == null) { + for(ProxiedPlayer pp : BungeeCord.getInstance().getPlayers()) { + if(pp.hasPermission("bungeecord.command.eag.reloadban")) { + pp.sendMessage(BanList.banChatMessagePrefix + ChatColor.WHITE + "Your Eaglercraftbungee bans.txt just got modified, it will be reloaded asap"); + pp.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Stop your server and check your config immediately if you don't know how this happened!!"); + } + } + } + parseListFrom(); + BungeeCord.getInstance().getLogger().info("Reload complete"); + }catch(Throwable t) { + if(cs != null) { + cs.sendMessage(banChatMessagePrefix + ChatColor.RED + "Could not reload 'bans.txt' list file"); + cs.sendMessage(banChatMessagePrefix + ChatColor.RED + "(Reason: " + t.toString() + ")"); + } + BungeeCord.getInstance().getLogger().severe("Could not reload 'bans.txt' list file"); + BungeeCord.getInstance().getLogger().severe("(Reason: " + t.toString() + ")"); + } + } + } + } + + private static void parseListFrom() { + userBans.clear(); + ipBans.clear(); + wildcardBans.clear(); + regexBans.clear(); + + int filePart = 0; + boolean replaceBukkit = false; + + for(String s : currentBanList) { + s = s.trim(); + if(s.length() <= 0) { + continue; + } + if(s.startsWith("#")) { + continue; + } + if(s.equals("[Usernames]")) { + filePart = 1; + }else if(s.equals("[Wildcards]")) { + filePart = 2; + }else if(s.equals("[Regex]")) { + filePart = 3; + }else if(s.equals("[IPs]")) { + filePart = 4; + }else if(s.equals("replace-bukkit=true")) { + replaceBukkit = true; + }else if(s.equals("replace-bukkit=false")) { + continue; + }else { + if(filePart == 1) { + int i = s.indexOf(':'); + if(i == -1) { + userBans.put(s.toLowerCase(), ""); + }else { + userBans.put(s.substring(0, i).trim().toLowerCase(), s.substring(i + 1).trim()); + } + }else if(filePart == 2) { + boolean ws = s.startsWith("*"); + boolean we = s.endsWith("*"); + if(!ws && !we) { + if(s.contains("*")) { + BungeeCord.getInstance().getLogger().severe("wildcard '" + s + "' contains a '*' not at the start/end of the string"); + }else { + BungeeCord.getInstance().getLogger().severe("wildcard '" + s + "' does not contain a '*' wildcard character"); + } + }else { + int total = (ws ? 1 : 0) + (we ? 1 : 0); + int t2 = 0; + for(char c : s.toCharArray()) { + if(c == '*') ++t2; + } + if(total != t2) { + BungeeCord.getInstance().getLogger().severe("wildcard '" + s + "' contains a '*' not at the start/end of the string"); + } + } + wildcardBans.add(s.toLowerCase()); + }else if(filePart == 3) { + Pattern p = null; + try { + p = Pattern.compile(s); + }catch(Throwable t) { + BungeeCord.getInstance().getLogger().severe("the regex " + s.toLowerCase() + " is invalid"); + BungeeCord.getInstance().getLogger().severe("Reason: " + t.getClass().getSimpleName() + ": " + t.getLocalizedMessage()); + } + if(p != null) { + regexBans.add(new RegexBan(s, p)); + } + }else if(filePart == 4) { + String ss = s; + int subnet = -1; + int i = s.indexOf('/'); + if(i != -1) { + String s2 = s.substring(i + 1); + s = s.substring(0, i); + try { + subnet = Integer.parseInt(s2); + }catch(Throwable t) { + BungeeCord.getInstance().getLogger().severe("the subnet '"+ s2 +"' for IP ban address " + s + " was invalid"); + subnet = -2; + } + } + if(subnet >= -1) { + try { + InetAddress aa = InetAddress.getByName(s); + if(aa instanceof Inet4Address) { + if(subnet == -1) { + subnet = 32; + } + ipBans.add(new IPBan4((Inet4Address)aa, ss, subnet)); + }else if(aa instanceof Inet6Address) { + if(subnet == -1) { + subnet = 128; + } + ipBans.add(new IPBan6((Inet6Address)aa, ss, subnet)); + }else { + throw new UnknownHostException("Only ipv4 and ipv6 addresses allowed in Eaglercraft"); + } + }catch(Throwable t) { + BungeeCord.getInstance().getLogger().severe("the IP ban address " + s + " could not be parsed"); + t.printStackTrace(); + } + } + } + } + } + + BungeeCord.getInstance().reconfigureBanCommands(replaceBukkit); + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/DomainBlacklist.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/DomainBlacklist.java new file mode 100644 index 0000000..c949208 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/DomainBlacklist.java @@ -0,0 +1,359 @@ +package net.md_5.bungee.eaglercraft; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.config.ConfigurationAdapter; +import net.md_5.bungee.config.Configuration; + +public class DomainBlacklist { + + public static final Collection regexBlacklist = new ArrayList(); + public static final Collection regexLocalBlacklist = new ArrayList(); + public static final Collection regexBlacklistReplit = new ArrayList(); + public static final Collection simpleWhitelist = new ArrayList(); + public static final File localBlacklist = new File("origin_blacklist.txt"); + private static Collection blacklistSubscriptions = null; + private static boolean blockOfflineDownload = false; + private static boolean blockAllReplits = false; + private static boolean localWhitelistMode = false; + private static boolean simpleWhitelistMode = false; + private static final HashSet brokenURLs = new HashSet(); + private static final HashSet brokenRegex = new HashSet(); + + public static final HashSet regexBlacklistReplitInternalStrings = new HashSet(); + public static final Collection regexBlacklistReplitInternal = new ArrayList(); + + static { + regexBlacklistReplitInternalStrings.add(".*repl(it)?\\..{1,5}$"); + for(String s : regexBlacklistReplitInternalStrings) { + regexBlacklistReplitInternal.add(Pattern.compile(s)); + } + } + + private static int updateRate = 15 * 60 * 1000; + private static long lastLocalUpdate = 0l; + private static long lastUpdate = 0; + + public static boolean test(String origin) { + synchronized(regexBlacklist) { + if(blockOfflineDownload && origin.equalsIgnoreCase("null")) { + return true; + } + if(simpleWhitelistMode) { + for(String st : simpleWhitelist) { + if(origin.equalsIgnoreCase(st)) { + return false; + } + } + } + if(localWhitelistMode || simpleWhitelistMode) { + if(!blockOfflineDownload && origin.equalsIgnoreCase("null")) { + return false; + } + for(Pattern m : regexLocalBlacklist) { + if(m.matcher(origin).matches()) { + return false; + } + } + return true; + }else { + if(blockAllReplits) { + for(Pattern m : regexBlacklistReplitInternal) { + if(m.matcher(origin).matches()) { + return true; + } + } + for(Pattern m : regexBlacklistReplit) { + if(m.matcher(origin).matches()) { + return true; + } + } + } + for(Pattern m : regexBlacklist) { + if(m.matcher(origin).matches()) { + return true; + } + } + for(Pattern m : regexLocalBlacklist) { + if(m.matcher(origin).matches()) { + return true; + } + } + return false; + } + } + } + + public static void init(BungeeCord bg) { + synchronized(regexBlacklist) { + brokenURLs.clear(); + brokenRegex.clear(); + regexBlacklist.clear(); + regexLocalBlacklist.clear(); + regexBlacklistReplit.clear(); + simpleWhitelist.clear(); + ConfigurationAdapter cfg2 = bg.getConfigurationAdapter(); + Configuration cfg = bg.config; + blacklistSubscriptions = cfg2.getBlacklistURLs(); + blockOfflineDownload = cfg.shouldBlacklistOfflineDownload(); + blockAllReplits = cfg.shouldBlacklistReplits(); + simpleWhitelistMode = cfg.isSimpleWhitelistEnabled(); + simpleWhitelist.addAll(cfg2.getBlacklistSimpleWhitelist()); + lastLocalUpdate = 0l; + lastUpdate = System.currentTimeMillis() - updateRate - 1000l; + update(); + } + } + + public static void update() { + long ct = System.currentTimeMillis(); + if((int)(ct - lastUpdate) > updateRate) { + lastUpdate = ct; + synchronized(regexBlacklist) { + if(blacklistSubscriptions != null) { + ArrayList newBlacklist = new ArrayList(); + ArrayList newReplitBlacklist = new ArrayList(); + HashSet newBlacklistSet = new HashSet(); + newBlacklistSet.addAll(regexBlacklistReplitInternalStrings); + for(String str : blacklistSubscriptions) { + try { + URL u; + try { + u = new URL(str); + }catch(MalformedURLException e) { + if(brokenURLs.add(str)) { + BungeeCord.getInstance().getLogger().severe("The blacklist subscription URL '" + str + "' is invalid"); + } + continue; + } + URLConnection cc = u.openConnection(); + if(cc instanceof HttpURLConnection) { + HttpURLConnection ccc = (HttpURLConnection)cc; + ccc.setRequestProperty("Accept", "text/plain,text/html,application/xhtml+xml,application/xml"); + ccc.setRequestProperty("User-Agent", "Mozilla/5.0 EaglercraftBungee/" + EaglercraftBungee.version); + } + cc.connect(); + BufferedReader is = new BufferedReader(new InputStreamReader(cc.getInputStream())); + String firstLine = is.readLine(); + if(firstLine == null) { + is.close(); + throw new IOException("Could not read line"); + } + firstLine = firstLine.trim(); + if(!firstLine.startsWith("#") || !firstLine.substring(1).trim().toLowerCase().startsWith("eaglercraft domain blacklist")) { + throw new IOException("File does not contain a list of domains"); + } + String ss; + while((ss = is.readLine()) != null) { + if((ss = ss.trim()).length() > 0) { + if(ss.startsWith("#")) { + ss = ss.substring(1).trim(); + if(ss.startsWith("replit-wildcard:")) { + ss = ss.substring(16).trim(); + if(newBlacklistSet.add(ss)) { + try { + newReplitBlacklist.add(Pattern.compile(ss)); + }catch(PatternSyntaxException shit) { + if(brokenRegex.add(ss)) { + BungeeCord.getInstance().getLogger().severe("the blacklist replit wildcard regex '" + ss + "' is invalid"); + continue; + } + } + brokenRegex.remove(ss); + } + } + continue; + } + if(newBlacklistSet.add(ss)) { + try { + newBlacklist.add(Pattern.compile(ss)); + }catch(PatternSyntaxException shit) { + if(brokenRegex.add(ss)) { + BungeeCord.getInstance().getLogger().severe("the blacklist regex '" + ss + "' is invalid"); + continue; + } + } + brokenRegex.remove(ss); + } + } + } + is.close(); + brokenURLs.remove(str); + }catch(Throwable t) { + if(brokenURLs.add(str)) { + BungeeCord.getInstance().getLogger().severe("the blacklist subscription URL '" + str + "' is invalid"); + } + t.printStackTrace(); + } + } + if(!newBlacklist.isEmpty()) { + regexBlacklist.clear(); + regexBlacklist.addAll(newBlacklist); + } + if(!newReplitBlacklist.isEmpty()) { + regexBlacklistReplit.clear(); + regexBlacklistReplit.addAll(newReplitBlacklist); + } + }else { + brokenURLs.clear(); + brokenRegex.clear(); + regexBlacklist.clear(); + lastLocalUpdate = 0l; + } + } + } + if(localBlacklist.exists()) { + long lastLocalEdit = localBlacklist.lastModified(); + if(lastLocalEdit != lastLocalUpdate) { + lastLocalUpdate = lastLocalEdit; + synchronized(regexBlacklist) { + try { + BufferedReader is = new BufferedReader(new FileReader(localBlacklist)); + regexLocalBlacklist.clear(); + localWhitelistMode = false; + boolean foundWhitelistStatement = false; + String ss; + while((ss = is.readLine()) != null) { + try { + if((ss = ss.trim()).length() > 0) { + if(!ss.startsWith("#")) { + regexLocalBlacklist.add(Pattern.compile(ss)); + }else { + String st = ss.substring(1).trim(); + if(st.startsWith("whitelistMode:")) { + foundWhitelistStatement = true; + String str = st.substring(14).trim().toLowerCase(); + localWhitelistMode = str.equals("true") || str.equals("on") || str.equals("1"); + } + } + } + }catch(PatternSyntaxException shit) { + BungeeCord.getInstance().getLogger().severe("the local " + (localWhitelistMode ? "whitelist" : "blacklist") + " regex '" + ss + "' is invalid"); + } + } + is.close(); + if(!foundWhitelistStatement) { + List newLines = new ArrayList(); + newLines.add("#whitelistMode: false"); + newLines.add(""); + BufferedReader is2 = new BufferedReader(new FileReader(localBlacklist)); + while((ss = is2.readLine()) != null) { + newLines.add(ss); + } + is2.close(); + PrintWriter os = new PrintWriter(new FileWriter(localBlacklist)); + for(String str : newLines) { + os.println(str); + } + os.close(); + lastLocalUpdate = localBlacklist.lastModified(); + } + BungeeCord.getInstance().getLogger().info("Reloaded '" + localBlacklist.getName() + "'."); + }catch(IOException ex) { + regexLocalBlacklist.clear(); + BungeeCord.getInstance().getLogger().severe("failed to read local " + (localWhitelistMode ? "whitelist" : "blacklist") + " file '" + localBlacklist.getName() + "'"); + ex.printStackTrace(); + } + } + } + }else { + synchronized(regexBlacklist) { + if(!regexLocalBlacklist.isEmpty()) { + BungeeCord.getInstance().getLogger().warning("the blacklist file '" + localBlacklist.getName() + "' has been deleted"); + } + regexLocalBlacklist.clear(); + } + } + } + + public static void addLocal(String o) { + String p = "^" + Pattern.quote(o.trim()) + "$"; + ArrayList lines = new ArrayList(); + if(localBlacklist.exists()) { + try { + BufferedReader is = new BufferedReader(new FileReader(localBlacklist)); + String ss; + while((ss = is.readLine()) != null) { + if((ss = ss.trim()).length() > 0) { + lines.add(ss); + } + } + is.close(); + }catch(IOException ex) { + // ? + } + } + if(lines.isEmpty()) { + lines.add("#whitelist false"); + lines.add(""); + } + if(!lines.contains(p)) { + lines.add(p); + try { + PrintWriter os = new PrintWriter(new FileWriter(localBlacklist)); + for(String s : lines) { + os.println(s); + } + os.close(); + lastLocalUpdate = 0l; + update(); + }catch(IOException ex) { + // ? + } + } + } + + public static boolean removeLocal(String o) { + String p = "^" + Pattern.quote(o.trim()) + "$"; + ArrayList lines = new ArrayList(); + if(localBlacklist.exists()) { + try { + BufferedReader is = new BufferedReader(new FileReader(localBlacklist)); + String ss; + while((ss = is.readLine()) != null) { + if((ss = ss.trim()).length() > 0) { + lines.add(ss); + } + } + is.close(); + }catch(IOException ex) { + // ? + } + } + if(lines.contains(p)) { + lines.remove(p); + try { + PrintWriter os = new PrintWriter(new FileWriter(localBlacklist)); + for(String s : lines) { + os.println(s); + } + os.close(); + lastLocalUpdate = 0l; + update(); + return true; + }catch(IOException ex) { + BungeeCord.getInstance().getLogger().severe("Failed to save '" + localBlacklist.getName() + "'"); + ex.printStackTrace(); + } + } + return false; + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/EaglercraftBungee.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/EaglercraftBungee.java new file mode 100644 index 0000000..8d16e6e --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/EaglercraftBungee.java @@ -0,0 +1,10 @@ +package net.md_5.bungee.eaglercraft; + +public class EaglercraftBungee { + + public static final String brand = "Eagtek"; + public static final String name = "EaglercraftBungee"; + public static final String version = "0.4.0"; // wtf does this even mean at this point + public static final boolean cracked = true; + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/ExpiringSet.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/ExpiringSet.java new file mode 100644 index 0000000..5edefac --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/ExpiringSet.java @@ -0,0 +1,71 @@ +package net.md_5.bungee.eaglercraft; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + +// note that there's a few things not implemented, but I don't care. + +public class ExpiringSet extends HashSet { + private final long expiration; + private final ExpiringEvent event; + + private final Map timestamps = new HashMap<>(); + + public ExpiringSet(long expiration) { + this.expiration = expiration; + this.event = null; + } + + public ExpiringSet(long expiration, ExpiringEvent event) { + this.expiration = expiration; + this.event = event; + } + + public interface ExpiringEvent { + void onExpiration(T item); + } + + public void checkForExpirations() { + Iterator 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); + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/GeneralDigest.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/GeneralDigest.java new file mode 100644 index 0000000..2780b3b --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/GeneralDigest.java @@ -0,0 +1,124 @@ +package net.md_5.bungee.eaglercraft; + +/** + * 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(); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/MOTDConnectionImpl.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/MOTDConnectionImpl.java new file mode 100644 index 0000000..c222e62 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/MOTDConnectionImpl.java @@ -0,0 +1,202 @@ +package net.md_5.bungee.eaglercraft; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.java_websocket.WebSocket; +import org.json.JSONArray; +import org.json.JSONObject; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.MOTD; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.config.MOTDCacheConfiguration; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public class MOTDConnectionImpl extends QueryConnectionImpl implements MOTD { + + private String line1; + private String line2; + private List players; + private int[] bitmap; + private int onlinePlayers; + private int maxPlayers; + private boolean hasIcon; + private boolean iconDirty; + private String subType; + + public MOTDConnectionImpl(ListenerInfo listener, InetAddress addr, WebSocket socket, String arg1) { + super(listener, addr, socket, "motd"); + String[] lns = listener.getMotd().split("\n"); + if(lns.length >= 1) { + line1 = lns[0]; + } + if(lns.length >= 2) { + line2 = lns[1]; + } + maxPlayers = listener.getMaxPlayers(); + onlinePlayers = BungeeCord.getInstance().getOnlineCount(); + players = new ArrayList(); + for(ProxiedPlayer pp : BungeeCord.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]; + setReturnType("motd"); + int i = arg1.indexOf('.'); + if(i > 0) { + subType = arg1.substring(i + 1); + if(subType.length() == 0) { + subType = "motd"; + } + }else { + subType = "motd"; + } + if(!subType.startsWith("noicon") && !subType.startsWith("cache.noicon")) { + iconDirty = hasIcon = listener.isIconSet(); + if(hasIcon) { + System.arraycopy(listener.getServerIconCache(), 0, bitmap, 0, 4096); + } + } + } + + @Override + public String getLine1() { + return line1; + } + + @Override + public String getLine2() { + return line2; + } + + @Override + public List 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 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; + } + + @Override + public void sendToUser() { + if(!isClosed()) { + JSONObject obj = new JSONObject(); + if(subType.startsWith("cache.anim")) { + obj.put("unsupported", true); + writeResponse(obj); + close(); + return; + }else if(subType.startsWith("cache")) { + JSONArray cacheControl = new JSONArray(); + MOTDCacheConfiguration cc = listener.getCacheConfig(); + if(cc.cacheServerListAnimation) { + cacheControl.put("animation"); + } + if(cc.cacheServerListResults) { + cacheControl.put("results"); + } + if(cc.cacheServerListTrending) { + cacheControl.put("trending"); + } + if(cc.cacheServerListPortfolios) { + cacheControl.put("portfolio"); + } + obj.put("cache", cacheControl); + obj.put("ttl", cc.cacheTTL); + }else { + MOTDCacheConfiguration cc = listener.getCacheConfig(); + obj.put("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.put(line1); + if(line2 != null && line2.length() > 0) motd.put(line2); + obj.put("motd", motd); + obj.put("icon", hasIcon && !noIcon); + obj.put("online", onlinePlayers); + obj.put("max", maxPlayers); + JSONArray playerz = new JSONArray(); + for(String s : players) { + playerz.put(s); + } + obj.put("players", playerz); + writeResponse(obj); + if(hasIcon && !noIcon && iconDirty && bitmap != null) { + byte[] iconPixels = new byte[16384]; + for(int i = 0; i < 4096; ++i) { + iconPixels[i * 4] = (byte)((bitmap[i] >> 16) & 0xFF); + iconPixels[i * 4 + 1] = (byte)((bitmap[i] >> 8) & 0xFF); + iconPixels[i * 4 + 2] = (byte)(bitmap[i] & 0xFF); + iconPixels[i * 4 + 3] = (byte)((bitmap[i] >> 24) & 0xFF); + } + writeResponseBinary(iconPixels); + iconDirty = false; + } + if(subType.startsWith("cache")) { + close(); + } + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/PluginEaglerSkins.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/PluginEaglerSkins.java new file mode 100644 index 0000000..54aea81 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/PluginEaglerSkins.java @@ -0,0 +1,122 @@ +package net.md_5.bungee.eaglercraft; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; + +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.plugin.PluginDescription; +import net.md_5.bungee.event.EventHandler; + +public class PluginEaglerSkins extends Plugin implements Listener { + + private final HashMap skinCollection = new HashMap(); + private final HashMap capeCollection = new HashMap(); + private final HashMap lastSkinLayerUpdate = new HashMap(); + + private static final int[] SKIN_DATA_SIZE = new int[] { 64*32*4, 64*64*4, -9, -9, 1, 64*64*4, -9 }; // 128 pixel skins crash clients + private static final int[] CAPE_DATA_SIZE = new int[] { 32*32*4, -9, 1 }; + + public PluginEaglerSkins() { + super(new PluginDescription("EaglerSkins", PluginEaglerSkins.class.getName(), "1.0.0", "LAX1DUDE", Collections.emptySet(), null)); + } + + public void onLoad() { + + } + + public void onEnable() { + getProxy().getPluginManager().registerListener(this, this); + } + + public void onDisable() { + + } + + @EventHandler + public void onPluginMessage(PluginMessageEvent event) { + if(event.getSender() instanceof UserConnection && event.getData().length > 0) { + String user = ((UserConnection)event.getSender()).getName(); + byte[] msg = event.getData(); + try { + event.setCancelled(true); + if("EAG|MySkin".equals(event.getTag())) { + if(!skinCollection.containsKey(user)) { + int t = (int)msg[0] & 0xFF; + if(t < SKIN_DATA_SIZE.length && msg.length == (SKIN_DATA_SIZE[t] + 1)) { + skinCollection.put(user, msg); + } + } + }else if("EAG|MyCape".equals(event.getTag())) { + if(!capeCollection.containsKey(user)) { + int t = (int)msg[0] & 0xFF; + if(t < CAPE_DATA_SIZE.length && msg.length == (CAPE_DATA_SIZE[t] + 2)) { + capeCollection.put(user, msg); + } + } + }else if("EAG|FetchSkin".equals(event.getTag())) { + if(msg.length > 2) { + String fetch = new String(msg, 2, msg.length - 2, StandardCharsets.UTF_8); + byte[] data; + if((data = skinCollection.get(fetch)) != null) { + byte[] conc = new byte[data.length + 2]; + conc[0] = msg[0]; conc[1] = msg[1]; //synchronization cookie + System.arraycopy(data, 0, conc, 2, data.length); + if((data = capeCollection.get(fetch)) != null) { + byte[] conc2 = new byte[conc.length + data.length]; + System.arraycopy(conc, 0, conc2, 0, conc.length); + System.arraycopy(data, 0, conc2, conc.length, data.length); + conc = conc2; + } + ((UserConnection)event.getSender()).sendData("EAG|UserSkin", conc); + } + } + }else if("EAG|SkinLayers".equals(event.getTag())) { + long millis = System.currentTimeMillis(); + Long lsu = lastSkinLayerUpdate.get(user); + if(lsu != null && millis - lsu < 700L) { // DoS protection + return; + } + lastSkinLayerUpdate.put(user, millis); + byte[] data; + if((data = capeCollection.get(user)) != null) { + data[1] = msg[0]; + }else { + data = new byte[] { (byte)2, msg[0], (byte)0 }; + capeCollection.put(user, data); + } + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + DataOutputStream dd = new DataOutputStream(bao); + dd.write(msg[0]); + dd.writeUTF(user); + byte[] bpacket = bao.toByteArray(); + for(ProxiedPlayer pl : getProxy().getPlayers()) { + if(!pl.getName().equals(user)) { + pl.sendData("EAG|SkinLayers", bpacket); + } + } + }else { + event.setCancelled(false); + } + }catch(Throwable t) { + // hacker + } + } + } + + @EventHandler + public void onPlayerDisconnect(PlayerDisconnectEvent event) { + String nm = event.getPlayer().getName(); + skinCollection.remove(nm); + capeCollection.remove(nm); + lastSkinLayerUpdate.remove(nm); + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/PluginEaglerVoice.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/PluginEaglerVoice.java new file mode 100644 index 0000000..efe8178 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/PluginEaglerVoice.java @@ -0,0 +1,248 @@ +package net.md_5.bungee.eaglercraft; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.UserConnection; +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.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.plugin.PluginDescription; +import net.md_5.bungee.event.EventHandler; + +public class PluginEaglerVoice extends Plugin implements Listener { + + private final boolean voiceEnabled; + + private final Map voicePlayers = new HashMap<>(); + private final Map> voiceRequests = new HashMap<>(); + private final Set voicePairs = new HashSet<>(); + + private static final int VOICE_SIGNAL_ALLOWED = 0; + private static final int VOICE_SIGNAL_REQUEST = 0; + private static final int VOICE_SIGNAL_CONNECT = 1; + private static final int VOICE_SIGNAL_DISCONNECT = 2; + private static final int VOICE_SIGNAL_ICE = 3; + private static final int VOICE_SIGNAL_DESC = 4; + private static final int VOICE_SIGNAL_GLOBAL = 5; + + public PluginEaglerVoice(boolean voiceEnabled) { + super(new PluginDescription("EaglerVoice", PluginEaglerVoice.class.getName(), "1.0.0", "ayunami2000", Collections.emptySet(), null)); + this.voiceEnabled = voiceEnabled; + } + + public void onLoad() { + + } + + public void onEnable() { + getProxy().getPluginManager().registerListener(this, this); + } + + public void onDisable() { + + } + + @EventHandler + public void onPluginMessage(PluginMessageEvent event) { + synchronized (voicePlayers) { + if (!voiceEnabled) return; + if (event.getSender() instanceof UserConnection && event.getData().length > 0) { + UserConnection connection = (UserConnection) event.getSender(); + String user = connection.getName(); + byte[] msg = event.getData(); + try { + if (!("EAG|Voice".equals(event.getTag()))) return; + event.setCancelled(true); + DataInputStream streamIn = new DataInputStream(new ByteArrayInputStream(msg)); + int sig = streamIn.read(); + switch (sig) { + case VOICE_SIGNAL_CONNECT: + if (voicePlayers.containsKey(user)) return; // user is already using voice chat + // send out packet for player joined voice + // notice: everyone on the server can see this packet!! however, it doesn't do anything but let clients know that the player has turned on voice chat + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.write(VOICE_SIGNAL_CONNECT); + dos.writeUTF(user); + byte[] out = baos.toByteArray(); + for (UserConnection conn : voicePlayers.values()) conn.sendData("EAG|Voice", out); + voicePlayers.put(user, connection); + for (String username : voicePlayers.keySet()) sendVoicePlayers(username); + break; + case VOICE_SIGNAL_DISCONNECT: + if (!voicePlayers.containsKey(user)) return; // user is not using voice chat + try { + String user2 = streamIn.readUTF(); + if (!voicePlayers.containsKey(user2)) return; + if (voicePairs.removeIf(pair -> (pair[0].equals(user) && pair[1].equals(user2)) || (pair[0].equals(user2) && pair[1].equals(user)))) { + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + DataOutputStream dos2 = new DataOutputStream(baos2); + dos2.write(VOICE_SIGNAL_DISCONNECT); + dos2.writeUTF(user); + voicePlayers.get(user2).sendData("EAG|Voice", baos2.toByteArray()); + baos2 = new ByteArrayOutputStream(); + dos2 = new DataOutputStream(baos2); + dos2.write(VOICE_SIGNAL_DISCONNECT); + dos2.writeUTF(user2); + connection.sendData("EAG|Voice", baos2.toByteArray()); + } + } catch (EOFException e) { + removeUser(user); + } + break; + case VOICE_SIGNAL_REQUEST: + if (!voicePlayers.containsKey(user)) return; // user is not using voice chat + String targetUser = streamIn.readUTF(); + if (user.equals(targetUser)) return; // prevent duplicates + if (checkVoicePair(user, targetUser)) return; // already paired + if (!voicePlayers.containsKey(targetUser)) return; // target user is not using voice chat + if (!voiceRequests.containsKey(user)) voiceRequests.put(user, new ExpiringSet<>(2000)); + if (voiceRequests.get(user).contains(targetUser)) return; + voiceRequests.get(user).add(targetUser); + + // check if other has requested earlier + if (voiceRequests.containsKey(targetUser) && voiceRequests.get(targetUser).contains(user)) { + if (voiceRequests.containsKey(targetUser)) { + voiceRequests.get(targetUser).remove(user); + if (voiceRequests.get(targetUser).isEmpty()) voiceRequests.remove(targetUser); + } + if (voiceRequests.containsKey(user)) { + voiceRequests.get(user).remove(targetUser); + if (voiceRequests.get(user).isEmpty()) voiceRequests.remove(user); + } + // send each other add data + voicePairs.add(new String[]{user, targetUser}); + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + DataOutputStream dos2 = new DataOutputStream(baos2); + dos2.write(VOICE_SIGNAL_CONNECT); + dos2.writeUTF(user); + dos2.writeBoolean(false); + voicePlayers.get(targetUser).sendData("EAG|Voice", baos2.toByteArray()); + baos2 = new ByteArrayOutputStream(); + dos2 = new DataOutputStream(baos2); + dos2.write(VOICE_SIGNAL_CONNECT); + dos2.writeUTF(targetUser); + dos2.writeBoolean(true); + connection.sendData("EAG|Voice", baos2.toByteArray()); + } + break; + case VOICE_SIGNAL_ICE: + case VOICE_SIGNAL_DESC: + if (!voicePlayers.containsKey(user)) return; // user is not using voice chat + String targetUser2 = streamIn.readUTF(); + if (checkVoicePair(user, targetUser2)) { + String data = streamIn.readUTF(); + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + DataOutputStream dos2 = new DataOutputStream(baos2); + dos2.write(sig); + dos2.writeUTF(user); + dos2.writeUTF(data); + voicePlayers.get(targetUser2).sendData("EAG|Voice", baos2.toByteArray()); + } + break; + default: + break; + } + } catch (Throwable t) { + // hacker + // t.printStackTrace(); // todo: remove in production + removeUser(user); + } + } + } + } + + @EventHandler + public void onPostLogin(PostLoginEvent event) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.write(VOICE_SIGNAL_ALLOWED); + dos.writeBoolean(voiceEnabled); + Collection servs = BungeeCord.getInstance().config.getICEServers(); + dos.write(servs.size()); + for(String str : servs) { + dos.writeUTF(str); + } + event.getPlayer().sendData("EAG|Voice", baos.toByteArray()); + sendVoicePlayers(event.getPlayer().getName()); + } catch (IOException ignored) { } + } + + @EventHandler + public void onPlayerDisconnect(PlayerDisconnectEvent event) { + String nm = event.getPlayer().getName(); + removeUser(nm); + } + + public void sendVoicePlayers(String name) { + synchronized (voicePlayers) { + if (!voiceEnabled) return; + if (!voicePlayers.containsKey(name)) return; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.write(VOICE_SIGNAL_GLOBAL); + Set mostlyGlobalPlayers = new HashSet<>(); + for (String username : voicePlayers.keySet()) { + if (username.equals(name)) continue; + if (voicePairs.stream().anyMatch(pair -> (pair[0].equals(name) && pair[1].equals(username)) || (pair[0].equals(username) && pair[1].equals(name)))) + continue; + mostlyGlobalPlayers.add(username); + } + if (mostlyGlobalPlayers.size() > 0) { + dos.writeInt(mostlyGlobalPlayers.size()); + for (String username : mostlyGlobalPlayers) dos.writeUTF(username); + voicePlayers.get(name).sendData("EAG|Voice", baos.toByteArray()); + } + } catch (IOException ignored) { + } + } + } + + public void removeUser(String name) { + synchronized (voicePlayers) { + voicePlayers.remove(name); + for (String username : voicePlayers.keySet()) { + if (!name.equals(username)) sendVoicePlayers(username); + } + for (String[] voicePair : voicePairs) { + String target = null; + if (voicePair[0].equals(name)) { + target = voicePair[1]; + } else if (voicePair[1].equals(name)) { + target = voicePair[0]; + } + if (target != null && voicePlayers.containsKey(target)) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.write(VOICE_SIGNAL_DISCONNECT); + dos.writeUTF(name); + voicePlayers.get(target).sendData("EAG|Voice", baos.toByteArray()); + } catch (IOException ignored) { + } + } + } + voicePairs.removeIf(pair -> pair[0].equals(name) || pair[1].equals(name)); + } + } + + private boolean checkVoicePair(String user1, String user2) { + return voicePairs.stream().anyMatch(pair -> (pair[0].equals(user1) && pair[1].equals(user2)) || (pair[0].equals(user2) && pair[1].equals(user1))); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/QueryConnectionImpl.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/QueryConnectionImpl.java new file mode 100644 index 0000000..04fd2b2 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/QueryConnectionImpl.java @@ -0,0 +1,121 @@ +package net.md_5.bungee.eaglercraft; + +import java.net.InetAddress; +import java.util.LinkedList; +import java.util.List; + +import org.java_websocket.WebSocket; + +import net.md_5.bungee.api.QueryConnection; +import net.md_5.bungee.api.config.ListenerInfo; + +public class QueryConnectionImpl implements QueryConnection { + + protected ListenerInfo listener; + protected InetAddress addr; + protected WebSocket socket; + protected String accept; + protected String responseType; + protected List packetBuffer = new LinkedList(); + protected long creationTime; + protected boolean keepAlive = false; + + public static String confirmHash = null; + + public QueryConnectionImpl(ListenerInfo listener, InetAddress addr, WebSocket socket, String accept) { + this.listener = listener; + this.addr = addr; + this.socket = socket; + this.accept = accept.toLowerCase(); + this.responseType = "unknown"; + this.creationTime = System.currentTimeMillis(); + } + + public void postMessage(String m) { + synchronized(packetBuffer) { + packetBuffer.add(m); + } + } + + @Override + public InetAddress getRemoteAddress() { + return addr; + } + + @Override + public ListenerInfo getListener() { + return listener; + } + + @Override + public String getAccept() { + return accept; + } + + @Override + public int availableRequests() { + synchronized(packetBuffer) { + return packetBuffer.size(); + } + } + + @Override + public String readRequestString() { + synchronized(packetBuffer) { + if(packetBuffer.size() > 0) { + return packetBuffer.remove(0); + }else { + return null; + } + } + } + + @Override + public long getConnectionTimestamp() { + return creationTime; + } + + @Override + public void writeResponseRaw(String msg) { + socket.send(msg); + } + + @Override + public void writeResponseBinary(byte[] blob) { + socket.send(blob); + } + + @Override + public void keepAlive(boolean yes) { + keepAlive = yes; + } + + @Override + public boolean shouldKeepAlive() { + return keepAlive; + } + + @Override + public boolean isClosed() { + return socket.isClosing() || socket.isClosed(); + } + + @Override + public void close() { + socket.close(); + } + + @Override + public void setReturnType(String type) { + if(!"unknown".equals(responseType) && !type.equalsIgnoreCase(responseType)) { + throw new IllegalStateException("Tried to change query return type to '" + type + "' when it was already set to '" + responseType + "'"); + } + responseType = type; + } + + @Override + public String getReturnType() { + return responseType; + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/RedirectServerInfo.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/RedirectServerInfo.java new file mode 100644 index 0000000..53388d7 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/RedirectServerInfo.java @@ -0,0 +1,63 @@ +package net.md_5.bungee.eaglercraft; + +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.Collections; + +import com.google.common.base.Preconditions; + +import net.md_5.bungee.api.Callback; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ServerPing; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public class RedirectServerInfo implements ServerInfo { + + private final String serverName; + private final String serverRedirect; + private final boolean restricted; + + @Override + public String getName() { + return serverName; + } + + @Override + public InetSocketAddress getAddress() { + return null; + } + + @Override + public String getRedirect() { + return serverRedirect; + } + + @Override + public Collection getPlayers() { + return Collections.emptyList(); + } + + @Override + public boolean canAccess(CommandSender p0) { + Preconditions.checkNotNull((Object) p0, (Object) "player"); + return !this.restricted || p0.hasPermission("bungeecord.server." + serverName); + } + + @Override + public void sendData(String p0, byte[] p1) { + + } + + @Override + public void ping(Callback p0) { + p0.done(null, new UnsupportedOperationException("Cannot ping a redirect server!")); + } + + public RedirectServerInfo(String serverName, String serverRedirect, boolean restricted) { + this.serverName = serverName; + this.serverRedirect = serverRedirect; + this.restricted = restricted; + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/SHA1Digest.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/SHA1Digest.java new file mode 100644 index 0000000..8d28673 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/SHA1Digest.java @@ -0,0 +1,270 @@ +package net.md_5.bungee.eaglercraft; + + +/** + * 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); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/WebSocketListener.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/WebSocketListener.java new file mode 100644 index 0000000..0c635b2 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/WebSocketListener.java @@ -0,0 +1,323 @@ +package net.md_5.bungee.eaglercraft; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; + +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.MOTD; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.event.WebsocketMOTDEvent; +import net.md_5.bungee.api.event.WebsocketQueryEvent; +import net.md_5.bungee.eaglercraft.WebSocketRateLimiter.RateLimit; + +public class WebSocketListener extends WebSocketServer { + + public static final String queryResponseBlocked = "{\"type\":\"blocked\"}"; + public static final String queryResponseLockout = "{\"type\":\"locked\"}"; + + public static final String ipBlockedString = "BLOCKED"; + public static final String ipLockedString = "LOCKED"; + + public static class PendingSocket { + public long openTime; + public InetAddress realAddress; + public String origin; + public boolean bypassBan; + protected PendingSocket(long openTime, InetAddress realAddress, String origin, boolean bypassBan) { + this.openTime = openTime; + this.realAddress = realAddress; + this.origin = origin; + this.bypassBan = bypassBan; + } + } + + private InetSocketAddress bungeeProxy; + private ProxyServer bungeeCord; + private boolean blockOriginless; + private ListenerInfo info; + private final WebSocketRateLimiter ratelimitIP; + private final WebSocketRateLimiter ratelimitLogin; + private final WebSocketRateLimiter ratelimitMOTD; + private final WebSocketRateLimiter ratelimitQuery; + + public WebSocketListener(ListenerInfo info, InetSocketAddress sock, ProxyServer bungeeCord) { + super(info.getHost()); + this.setReuseAddr(true); + this.setTcpNoDelay(true); + this.setConnectionLostTimeout(20); + this.start(); + this.info = info; + this.bungeeProxy = sock; + this.bungeeCord = bungeeCord; + this.blockOriginless = ((BungeeCord)bungeeCord).config.shouldBlacklistOriginless(); + this.ratelimitIP = info.getRateLimitIP(); + this.ratelimitLogin = info.getRateLimitLogin(); + this.ratelimitMOTD = info.getRateLimitMOTD(); + this.ratelimitQuery = info.getRateLimitQuery(); + if(this.ratelimitIP != null) { + this.ratelimitIP.resetLimiters(); + } + if(this.ratelimitLogin != null) { + this.ratelimitLogin.resetLimiters(); + } + if(this.ratelimitMOTD != null) { + this.ratelimitMOTD.resetLimiters(); + } + if(this.ratelimitQuery != null) { + this.ratelimitQuery.resetLimiters(); + } + } + + @Override + public void onClose(WebSocket arg0, int arg1, String arg2, boolean arg3) { + Object o = arg0.getAttachment(); + if(o != null) { + if(o instanceof WebSocketProxy) { + ((WebSocketProxy)arg0.getAttachment()).killConnection(); + } + } + } + + @Override + public void onError(WebSocket arg0, Exception arg1) { + arg1.printStackTrace(); + } + + @Override + public void onMessage(WebSocket arg0, String arg1) { + Object o = arg0.getAttachment(); + if(o != null) { + if(o instanceof PendingSocket) { + InetAddress realAddr = ((PendingSocket)o).realAddress; + arg1 = arg1.trim().toLowerCase(); + QueryConnectionImpl con; + if(arg1.startsWith("accept:")) { + arg1 = arg1.substring(7).trim(); + WebsocketQueryEvent evt; + if(arg1.startsWith("motd")) { + if(info.isAllowMOTD()) { + if(ratelimitMOTD != null && !BanList.isBlockedBan(realAddr)) { + RateLimit l = ratelimitMOTD.rateLimit(realAddr); + if(l.blocked()) { + if(l == RateLimit.LIMIT) { + arg0.send(queryResponseBlocked); + }else if(l == RateLimit.NOW_LOCKED_OUT) { + arg0.send(queryResponseLockout); + } + arg0.close(); + return; + } + } + con = new MOTDConnectionImpl(info, realAddr, arg0, arg1); + evt = new WebsocketMOTDEvent((MOTD)con); + }else { + arg0.send(queryResponseBlocked); + arg0.close(); + return; + } + }else { + if(QueryConnectionImpl.confirmHash != null && arg1.equalsIgnoreCase(QueryConnectionImpl.confirmHash)) { + QueryConnectionImpl.confirmHash = null; + arg0.send("OK"); + arg0.close(); + return; + }else if(info.isAllowQuery()) { + if(ratelimitQuery != null && !BanList.isBlockedBan(realAddr)) { + RateLimit l = ratelimitQuery.rateLimit(realAddr); + if(l.blocked()) { + if(l == RateLimit.LIMIT) { + arg0.send(queryResponseBlocked); + }else if(l == RateLimit.NOW_LOCKED_OUT) { + arg0.send(queryResponseLockout); + } + arg0.close(); + return; + } + } + con = new QueryConnectionImpl(info, realAddr, arg0, arg1); + evt = new WebsocketQueryEvent(con); + }else { + arg0.send(queryResponseBlocked); + arg0.close(); + return; + } + } + BungeeCord.getInstance().getPluginManager().callEvent(evt); + if(!con.isClosed() && (con instanceof MOTDConnectionImpl)) { + ((MOTDConnectionImpl)con).sendToUser(); + } + if(!con.shouldKeepAlive() && !con.isClosed()) { + con.close(); + }else { + if(!arg0.isClosed()) { + arg0.setAttachment(con); + } + } + }else { + arg0.close(); + } + return; + }else if(o instanceof QueryConnectionImpl) { + ((QueryConnectionImpl)o).postMessage(arg1); + } + }else { + arg0.close(); + } + } + + @Override + public void onMessage(WebSocket arg0, ByteBuffer arg1) { + Object o = arg0.getAttachment(); + if(o == null || (o instanceof PendingSocket)) { + InetAddress realAddr; + if(o == null) { + realAddr = arg0.getRemoteSocketAddress().getAddress(); + }else { + realAddr = ((PendingSocket)o).realAddress; + } + if(ratelimitLogin != null && !BanList.isBlockedBan(realAddr)) { + RateLimit l = ratelimitLogin.rateLimit(realAddr); + if(l.blocked()) { + if(l == RateLimit.LIMIT) { + arg0.send(createRawKickPacket("BLOCKED")); + }else if(l == RateLimit.NOW_LOCKED_OUT) { + arg0.send(createRawKickPacket("LOCKED")); + } + arg0.close(); + return; + } + } + WebSocketProxy proxyObj = new WebSocketProxy(arg0, realAddr, ((PendingSocket)o).origin, bungeeProxy); + arg0.setAttachment(proxyObj); + if(!proxyObj.connect()) { + System.err.println("loopback to '" + bungeeProxy.toString() + "' failed - " + realAddr); + arg0.close(); + return; + } + o = proxyObj; + } + if(o != null) { + if(o instanceof WebSocketProxy) { + ((WebSocketProxy)o).sendPacket(arg1); + }else { + arg0.close(); + } + } + } + + @Override + public void onOpen(WebSocket arg0, ClientHandshake arg1) { + String origin = arg1.getFieldValue("Origin"); + if(origin != null) { + int idx = origin.indexOf("://"); + if(idx != -1) { + origin = origin.substring(idx + 3); + } + origin = origin.trim().toLowerCase(); + if(DomainBlacklist.test(origin)) { + arg0.send(createRawKickPacket("End of Stream")); + arg0.close(); + return; + } + }else { + if(blockOriginless) { + arg0.send(createRawKickPacket("End of Stream")); + arg0.close(); + return; + } + } + String ua = arg1.getFieldValue("User-Agent"); + if(blockOriginless && (ua == null || (ua = ua.toLowerCase()).contains("java-websocket") || ua.contains("tootallnate") || ua.contains("eaglercraft"))) { + arg0.send(createRawKickPacket("End of Stream")); + arg0.close(); + return; + } + InetAddress addr; + if(info.hasForwardedHeaders()) { + String s = arg1.getFieldValue(info.getForwardedIPHeader()); + if(s != null) { + try { + addr = InetAddress.getByName(s.split(",", 2)[0]); + }catch(UnknownHostException e) { + System.out.println("invalid '" + info.getForwardedIPHeader() + "' header - " + e.toString()); + arg0.close(); + return; + } + }else { + addr = arg0.getRemoteSocketAddress().getAddress(); + } + }else { + addr = arg0.getRemoteSocketAddress().getAddress(); + } + boolean bypassBan = BanList.isBlockedBan(addr); + if(!bypassBan && ratelimitIP != null) { + RateLimit l = ratelimitIP.rateLimit(addr); + if(l.blocked()) { + if(l == RateLimit.LIMIT) { + arg0.send(ipBlockedString); + }else if(l == RateLimit.NOW_LOCKED_OUT) { + arg0.send(ipLockedString); + } + arg0.close(); + return; + } + } + arg0.setAttachment(new PendingSocket(System.currentTimeMillis(), addr, origin, bypassBan)); + } + + @Override + public void onStart() { + } + + public void closeInactiveSockets() { + for(WebSocket w : this.getConnections()) { + Object o = w.getAttachment(); + if(o == null) { + w.close(); + }else if(o instanceof PendingSocket) { + if(System.currentTimeMillis() - ((PendingSocket)o).openTime > 1500l) { + w.close(); + } + } + } + } + + @Override + public void stop() throws IOException, InterruptedException { + for(WebSocket w : this.getConnections()) { + Object o = w.getAttachment(); + if(o != null && o instanceof WebSocketProxy) { + ((WebSocketProxy)o).killConnection(); + } + } + super.stop(); + } + + private byte[] createRawKickPacket(String str) { + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + DataOutputStream dout = new DataOutputStream(bao); + try { + dout.write(255); + dout.writeShort(str.length()); + dout.writeChars(str); + return bao.toByteArray(); + }catch(IOException e) { + return new byte[] { (byte)255, 0, 0 }; + } + } + + public ListenerInfo getInfo() { + return info; + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/WebSocketProxy.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/WebSocketProxy.java new file mode 100644 index 0000000..2bd1be1 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/WebSocketProxy.java @@ -0,0 +1,130 @@ +package net.md_5.bungee.eaglercraft; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.HashMap; + +import org.java_websocket.WebSocket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +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.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; + +/** + * Not the ideal solution but what are we supposed to do + * + */ +public class WebSocketProxy extends SimpleChannelInboundHandler { + + private WebSocket client; + private InetSocketAddress tcpListener; + private InetSocketAddress localAddress; + private InetAddress realRemoteAddr; + private String origin; + private NioSocketChannel tcpChannel; + + private static final EventLoopGroup group = new NioEventLoopGroup(4); + public static final HashMap localToRemote = new HashMap(); + public static final HashMap origins = new HashMap(); + + public WebSocketProxy(WebSocket w, InetAddress remoteAddr, String originz, InetSocketAddress addr) { + client = w; + realRemoteAddr = remoteAddr; + origin = originz; + tcpListener = addr; + tcpChannel = null; + } + + public void killConnection() { + synchronized(localToRemote) { + localToRemote.remove(localAddress); + origins.remove(localAddress); + } + if(tcpChannel != null && tcpChannel.isOpen()) { + try { + tcpChannel.disconnect().sync(); + } catch (InterruptedException e) { + ; + } + } + } + + public boolean connect() { + try { + if(tcpChannel == null) { + Bootstrap clientBootstrap = new Bootstrap(); + clientBootstrap.group(group); + clientBootstrap.channel(NioSocketChannel.class); + clientBootstrap.remoteAddress(tcpListener); + clientBootstrap.option(ChannelOption.TCP_NODELAY, true); + clientBootstrap.handler(new ChannelInitializer() { + protected void initChannel(SocketChannel socketChannel) throws Exception { + socketChannel.pipeline().addLast(WebSocketProxy.this); + socketChannel.closeFuture().addListener(new GenericFutureListener>() { + @Override + public void operationComplete(Future paramF) throws Exception { + synchronized(localToRemote) { + localToRemote.remove(localAddress); + origins.remove(localAddress); + } + } + }); + } + }); + tcpChannel = (NioSocketChannel) clientBootstrap.connect().sync().channel(); + synchronized(localToRemote) { + localToRemote.put(localAddress = tcpChannel.localAddress(), realRemoteAddr); + if(origin != null) { + origins.put(localAddress, origin); + } + } + return true; + } + }catch(Throwable t) { + t.printStackTrace(); + } + return false; + } + + @Override + protected void messageReceived(ChannelHandlerContext arg0, ByteBuf buffer) throws Exception { + ByteBuffer toSend = ByteBuffer.allocateDirect(buffer.capacity()); + toSend.put(buffer.nioBuffer()); + toSend.flip(); + if (client.isOpen()) { + client.send(toSend); + } else { + killConnection(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + } + + public void sendPacket(ByteBuffer arg1) { + if(tcpChannel != null && tcpChannel.isOpen()) { + tcpChannel.write(Unpooled.wrappedBuffer(arg1)); + } + } + + public void finalize() { + synchronized(localToRemote) { + localToRemote.remove(localAddress); + origins.remove(localAddress); + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/WebSocketRateLimiter.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/WebSocketRateLimiter.java new file mode 100644 index 0000000..f8d60a7 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/WebSocketRateLimiter.java @@ -0,0 +1,171 @@ +package net.md_5.bungee.eaglercraft; + +import java.net.InetAddress; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class WebSocketRateLimiter { + + public static enum RateLimit { + NONE, LIMIT, LOCKED_OUT, NOW_LOCKED_OUT; + public boolean blocked() { + return this != NONE; + } + } + + public final int period; + public final int limit; + public final int lockoutLimit; + public final int lockoutTime; + public final Collection exceptions; + + protected final Map ratelimiters = new HashMap(); + + public WebSocketRateLimiter(int period, int limit, int lockoutLimit, int lockoutTime, Collection exceptions) { + this.period = period; + this.limit = limit; + this.lockoutLimit = lockoutLimit; + this.lockoutTime = lockoutTime; + this.exceptions = exceptions; + } + + protected static class RateLimiter { + + protected final WebSocketRateLimiter limiterConfig; + + protected RateLimiter(WebSocketRateLimiter limiterConfig) { + this.limiterConfig = limiterConfig; + this.cooldownTimestamp = System.currentTimeMillis(); + } + + protected int requestCounter = 0; + protected long lockoutTimestamp = 0l; + protected long cooldownTimestamp; + + private boolean checkLockout(long currentTimeMillis) { + if(lockoutTimestamp > 0l) { + if(currentTimeMillis - lockoutTimestamp < (long)(limiterConfig.lockoutTime * 1000l)) { + return true; + }else { + lockoutTimestamp = 0l; + requestCounter = 0; + cooldownTimestamp = currentTimeMillis; + } + } + return false; + } + + private boolean checkCooldown(long currentTimeMillis) { + long cooldownIncrement = limiterConfig.period * 1000 / limiterConfig.limit; + while(currentTimeMillis - cooldownTimestamp > cooldownIncrement && requestCounter > 0) { + --requestCounter; + cooldownTimestamp += cooldownIncrement; + } + if(requestCounter == 0) { + cooldownTimestamp = currentTimeMillis; + return false; + }else { + return requestCounter >= limiterConfig.limit; + } + } + + protected RateLimit increment() { + long t = System.currentTimeMillis(); + if(checkLockout(t)) { + return RateLimit.LOCKED_OUT; + } + ++requestCounter; + boolean blockByCooldown = checkCooldown(t); + if(requestCounter >= limiterConfig.lockoutLimit) { + requestCounter = 0; + cooldownTimestamp = t; + lockoutTimestamp = t; + return RateLimit.NOW_LOCKED_OUT; + } + if(blockByCooldown) { + return RateLimit.LIMIT; + }else { + return RateLimit.NONE; + } + } + + protected RateLimit checkLimited() { + long t = System.currentTimeMillis(); + if(checkLockout(t)) { + return RateLimit.LOCKED_OUT; + }else if(checkCooldown(t)) { + return RateLimit.LIMIT; + }else { + return RateLimit.NONE; + } + } + + protected boolean checkClear() { + long t = System.currentTimeMillis(); + if(checkLockout(t) || checkCooldown(t)) { + return false; + }else if(requestCounter > 0) { + return false; + }else { + return true; + } + } + + } + + public void resetLimiters() { + synchronized(ratelimiters) { + ratelimiters.clear(); + } + } + + public void deleteClearLimiters() { + synchronized(ratelimiters) { + Iterator itr = ratelimiters.values().iterator(); + while(itr.hasNext()) { + if(itr.next().checkClear()) { + itr.remove(); + } + } + } + } + + public RateLimit checkLimit(InetAddress identifier) { + return checkLimit(identifier.getHostAddress()); + } + + public RateLimit rateLimit(InetAddress identifier) { + return rateLimit(identifier.getHostAddress()); + } + + public RateLimit checkLimit(String identifier) { + if(exceptions.contains(identifier)) { + return RateLimit.NONE; + } + synchronized(ratelimiters) { + RateLimiter l = ratelimiters.get(identifier); + if(l == null) { + return RateLimit.NONE; + }else { + return l.checkLimited(); + } + } + } + + public RateLimit rateLimit(String identifier) { + if(exceptions.contains(identifier)) { + return RateLimit.NONE; + } + synchronized(ratelimiters) { + RateLimiter l = ratelimiters.get(identifier); + if(l == null) { + l = new RateLimiter(this); + ratelimiters.put(identifier, l); + } + return l.increment(); + } + } + +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/sun/net/util/IPAddressUtil.java b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/sun/net/util/IPAddressUtil.java new file mode 100644 index 0000000..371d075 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/eaglercraft/sun/net/util/IPAddressUtil.java @@ -0,0 +1,321 @@ +package net.md_5.bungee.eaglercraft.sun.net.util; + +import java.net.URL; +import java.util.Arrays; + +public class IPAddressUtil { + private static final int INADDR4SZ = 4; + + private static final int INADDR16SZ = 16; + + private static final int INT16SZ = 2; + + private static final long L_IPV6_DELIMS = 0L; + + private static final long H_IPV6_DELIMS = 671088640L; + + private static final long L_GEN_DELIMS = -8935000888854970368L; + + private static final long H_GEN_DELIMS = 671088641L; + + private static final long L_AUTH_DELIMS = 288230376151711744L; + + private static final long H_AUTH_DELIMS = 671088641L; + + private static final long L_COLON = 288230376151711744L; + + private static final long H_COLON = 0L; + + private static final long L_SLASH = 140737488355328L; + + private static final long H_SLASH = 0L; + + private static final long L_BACKSLASH = 0L; + + private static final long H_BACKSLASH = 268435456L; + + private static final long L_NON_PRINTABLE = 4294967295L; + + private static final long H_NON_PRINTABLE = -9223372036854775808L; + + private static final long L_EXCLUDE = -8935000884560003073L; + + private static final long H_EXCLUDE = -9223372035915251711L; + + public static byte[] textToNumericFormatV4(String paramString) { + byte[] arrayOfByte = new byte[4]; + long l = 0L; + byte b1 = 0; + boolean bool = true; + int i = paramString.length(); + if (i == 0 || i > 15) + return null; + for (byte b2 = 0; b2 < i; b2++) { + char c = paramString.charAt(b2); + if (c == '.') { + if (bool || l < 0L || l > 255L || b1 == 3) + return null; + arrayOfByte[b1++] = (byte) (int) (l & 0xFFL); + l = 0L; + bool = true; + } else { + int j = Character.digit(c, 10); + if (j < 0) + return null; + l *= 10L; + l += j; + bool = false; + } + } + if (bool || l < 0L || l >= 1L << (4 - b1) * 8) + return null; + switch (b1) { + case 0 : + arrayOfByte[0] = (byte) (int) (l >> 24L & 0xFFL); + case 1 : + arrayOfByte[1] = (byte) (int) (l >> 16L & 0xFFL); + case 2 : + arrayOfByte[2] = (byte) (int) (l >> 8L & 0xFFL); + case 3 : + arrayOfByte[3] = (byte) (int) (l >> 0L & 0xFFL); + break; + } + return arrayOfByte; + } + + public static byte[] textToNumericFormatV6(String paramString) { + if (paramString.length() < 2) + return null; + char[] arrayOfChar = paramString.toCharArray(); + byte[] arrayOfByte1 = new byte[16]; + int j = arrayOfChar.length; + int k = paramString.indexOf("%"); + if (k == j - 1) + return null; + if (k != -1) + j = k; + byte b = -1; + byte b1 = 0, b2 = 0; + if (arrayOfChar[b1] == ':' && arrayOfChar[++b1] != ':') + return null; + byte b3 = b1; + boolean bool = false; + int i = 0; + while (b1 < j) { + char c = arrayOfChar[b1++]; + int m = Character.digit(c, 16); + if (m != -1) { + i <<= 4; + i |= m; + if (i > 65535) + return null; + bool = true; + continue; + } + if (c == ':') { + b3 = b1; + if (!bool) { + if (b != -1) + return null; + b = b2; + continue; + } + if (b1 == j) + return null; + if (b2 + 2 > 16) + return null; + arrayOfByte1[b2++] = (byte) (i >> 8 & 0xFF); + arrayOfByte1[b2++] = (byte) (i & 0xFF); + bool = false; + i = 0; + continue; + } + if (c == '.' && b2 + 4 <= 16) { + String str = paramString.substring(b3, j); + byte b4 = 0; + int n = 0; + while ((n = str.indexOf('.', n)) != -1) { + b4++; + n++; + } + if (b4 != 3) + return null; + byte[] arrayOfByte = textToNumericFormatV4(str); + if (arrayOfByte == null) + return null; + for (byte b5 = 0; b5 < 4; b5++) + arrayOfByte1[b2++] = arrayOfByte[b5]; + bool = false; + break; + } + return null; + } + if (bool) { + if (b2 + 2 > 16) + return null; + arrayOfByte1[b2++] = (byte) (i >> 8 & 0xFF); + arrayOfByte1[b2++] = (byte) (i & 0xFF); + } + if (b != -1) { + int m = b2 - b; + if (b2 == 16) + return null; + for (b1 = 1; b1 <= m; b1++) { + arrayOfByte1[16 - b1] = arrayOfByte1[b + m - b1]; + arrayOfByte1[b + m - b1] = 0; + } + b2 = 16; + } + if (b2 != 16) + return null; + byte[] arrayOfByte2 = convertFromIPv4MappedAddress(arrayOfByte1); + if (arrayOfByte2 != null) + return arrayOfByte2; + return arrayOfByte1; + } + + public static boolean isIPv4LiteralAddress(String paramString) { + return (textToNumericFormatV4(paramString) != null); + } + + public static boolean isIPv6LiteralAddress(String paramString) { + return (textToNumericFormatV6(paramString) != null); + } + + public static byte[] convertFromIPv4MappedAddress(byte[] paramArrayOfbyte) { + if (isIPv4MappedAddress(paramArrayOfbyte)) { + byte[] arrayOfByte = new byte[4]; + System.arraycopy(paramArrayOfbyte, 12, arrayOfByte, 0, 4); + return arrayOfByte; + } + return null; + } + + private static boolean isIPv4MappedAddress(byte[] paramArrayOfbyte) { + if (paramArrayOfbyte.length < 16) + return false; + if (paramArrayOfbyte[0] == 0 && paramArrayOfbyte[1] == 0 && paramArrayOfbyte[2] == 0 && paramArrayOfbyte[3] == 0 + && paramArrayOfbyte[4] == 0 && paramArrayOfbyte[5] == 0 && paramArrayOfbyte[6] == 0 + && paramArrayOfbyte[7] == 0 && paramArrayOfbyte[8] == 0 && paramArrayOfbyte[9] == 0 + && paramArrayOfbyte[10] == -1 && paramArrayOfbyte[11] == -1) + return true; + return false; + } + + public static boolean match(char paramChar, long paramLong1, long paramLong2) { + if (paramChar < '@') + return ((1L << paramChar & paramLong1) != 0L); + return false; + } + + public static int scan(String paramString, long paramLong1, long paramLong2) { + byte b = -1; + int i; + if (paramString == null || (i = paramString.length()) == 0) + return -1; + boolean bool = false; + while (++b < i && !(bool = match(paramString.charAt(b), paramLong1, paramLong2))); + if (bool) + return b; + return -1; + } + + public static int scan(String paramString, long paramLong1, long paramLong2, char[] paramArrayOfchar) { + byte b = -1; + int i; + if (paramString == null || (i = paramString.length()) == 0) + return -1; + boolean bool = false; + char c2 = paramArrayOfchar[0]; + char c1; + while (++b < i && !(bool = match(c1 = paramString.charAt(b), paramLong1, paramLong2))) { + if (c1 >= c2 && Arrays.binarySearch(paramArrayOfchar, c1) > -1) { + bool = true; + break; + } + } + if (bool) + return b; + return -1; + } + + private static String describeChar(char paramChar) { + if (paramChar < ' ' || paramChar == '') { + if (paramChar == '\n') + return "LF"; + if (paramChar == '\r') + return "CR"; + return "control char (code=" + paramChar + ")"; + } + if (paramChar == '\\') + return "'\\'"; + return "'" + paramChar + "'"; + } + + private static String checkUserInfo(String paramString) { + int i = scan(paramString, -9223231260711714817L, -9223372035915251711L); + if (i >= 0) + return "Illegal character found in user-info: " + describeChar(paramString.charAt(i)); + return null; + } + + private static String checkHost(String paramString) { + if (paramString.startsWith("[") && paramString.endsWith("]")) { + paramString = paramString.substring(1, paramString.length() - 1); + if (isIPv6LiteralAddress(paramString)) { + int j = paramString.indexOf('%'); + if (j >= 0) { + j = scan(paramString = paramString.substring(j), 4294967295L, -9223372036183687168L); + if (j >= 0) + return "Illegal character found in IPv6 scoped address: " + describeChar(paramString.charAt(j)); + } + return null; + } + return "Unrecognized IPv6 address format"; + } + int i = scan(paramString, -8935000884560003073L, -9223372035915251711L); + if (i >= 0) + return "Illegal character found in host: " + describeChar(paramString.charAt(i)); + return null; + } + + private static String checkAuth(String paramString) { + int i = scan(paramString, -9223231260711714817L, -9223372036586340352L); + if (i >= 0) + return "Illegal character found in authority: " + describeChar(paramString.charAt(i)); + return null; + } + + public static String checkAuthority(URL paramURL) { + if (paramURL == null) + return null; + String str1; + String str2; + if ((str1 = checkUserInfo(str2 = paramURL.getUserInfo())) != null) + return str1; + String str3; + if ((str1 = checkHost(str3 = paramURL.getHost())) != null) + return str1; + if (str3 == null && str2 == null) + return checkAuth(paramURL.getAuthority()); + return null; + } + + public static String checkExternalForm(URL paramURL) { + if (paramURL == null) + return null; + String str; + int i = scan(str = paramURL.getUserInfo(), 140741783322623L, Long.MIN_VALUE); + if (i >= 0) + return "Illegal character found in authority: " + describeChar(str.charAt(i)); + if ((str = checkHostString(paramURL.getHost())) != null) + return str; + return null; + } + + public static String checkHostString(String paramString) { + if (paramString == null) + return null; + return null; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/event/EventBus.java b/eaglerbungee/src/main/java/net/md_5/bungee/event/EventBus.java new file mode 100644 index 0000000..2afbbbd --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/event/EventBus.java @@ -0,0 +1,202 @@ +package net.md_5.bungee.event; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class EventBus +{ + + private final Map, Map>> byListenerAndPriority = new HashMap<>(); + private final Map, EventHandlerMethod[]> byEventBaked = new HashMap<>(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Logger logger; + + public EventBus() + { + this( null ); + } + + public EventBus(Logger logger) + { + this.logger = ( logger == null ) ? Logger.getGlobal() : logger; + } + + public void post(Object event) + { + lock.readLock().lock(); + try + { + EventHandlerMethod[] handlers = byEventBaked.get( event.getClass() ); + if ( handlers != null ) + { + for ( EventHandlerMethod method : handlers ) + { + try + { + method.invoke( event ); + } catch ( IllegalAccessException ex ) + { + throw new Error( "Method became inaccessible: " + event, ex ); + } catch ( IllegalArgumentException ex ) + { + throw new Error( "Method rejected target/argument: " + event, ex ); + } catch ( InvocationTargetException ex ) + { + logger.log( Level.WARNING, MessageFormat.format( "Error dispatching event {0} to listener {1}", event, method.getListener() ), ex.getCause() ); + } + } + } + } finally + { + lock.readLock().unlock(); + } + } + + private Map, Map>> findHandlers(Object listener) + { + Map, Map>> handler = new HashMap<>(); + for ( Method m : listener.getClass().getDeclaredMethods() ) + { + EventHandler annotation = m.getAnnotation( EventHandler.class ); + if ( annotation != null ) + { + Class[] params = m.getParameterTypes(); + if ( params.length != 1 ) + { + logger.log( Level.INFO, "Method {0} in class {1} annotated with {2} does not have single argument", new Object[] + { + m, listener.getClass(), annotation + } ); + continue; + } + Map> prioritiesMap = handler.get( params[0] ); + if ( prioritiesMap == null ) + { + prioritiesMap = new HashMap<>(); + handler.put( params[0], prioritiesMap ); + } + Set priority = prioritiesMap.get( annotation.priority() ); + if ( priority == null ) + { + priority = new HashSet<>(); + prioritiesMap.put( annotation.priority(), priority ); + } + priority.add( m ); + } + } + return handler; + } + + public void register(Object listener) + { + Map, Map>> handler = findHandlers( listener ); + lock.writeLock().lock(); + try + { + for ( Map.Entry, Map>> e : handler.entrySet() ) + { + Map> prioritiesMap = byListenerAndPriority.get( e.getKey() ); + if ( prioritiesMap == null ) + { + prioritiesMap = new HashMap<>(); + byListenerAndPriority.put( e.getKey(), prioritiesMap ); + } + for ( Map.Entry> entry : e.getValue().entrySet() ) + { + Map currentPriorityMap = prioritiesMap.get( entry.getKey() ); + if ( currentPriorityMap == null ) + { + currentPriorityMap = new HashMap<>(); + prioritiesMap.put( entry.getKey(), currentPriorityMap ); + } + Method[] baked = new Method[ entry.getValue().size() ]; + currentPriorityMap.put( listener, entry.getValue().toArray( baked ) ); + } + bakeHandlers( e.getKey() ); + } + } finally + { + lock.writeLock().unlock(); + } + } + + public void unregister(Object listener) + { + Map, Map>> handler = findHandlers( listener ); + lock.writeLock().lock(); + try + { + for ( Map.Entry, Map>> e : handler.entrySet() ) + { + Map> prioritiesMap = byListenerAndPriority.get( e.getKey() ); + if ( prioritiesMap != null ) + { + for ( Byte priority : e.getValue().keySet() ) + { + Map currentPriority = prioritiesMap.get( priority ); + if ( currentPriority != null ) + { + currentPriority.remove( listener ); + if ( currentPriority.isEmpty() ) + { + prioritiesMap.remove( priority ); + } + } + } + if ( prioritiesMap.isEmpty() ) + { + byListenerAndPriority.remove( e.getKey() ); + } + } + bakeHandlers( e.getKey() ); + } + } finally + { + lock.writeLock().unlock(); + } + } + + /** + * Shouldn't be called without first locking the writeLock; intended for use + * only inside {@link #register(java.lang.Object) register(Object)} or + * {@link #unregister(java.lang.Object) unregister(Object)}. + */ + private void bakeHandlers(Class eventClass) + { + Map> handlersByPriority = byListenerAndPriority.get( eventClass ); + if ( handlersByPriority != null ) + { + List handlersList = new ArrayList<>( handlersByPriority.size() * 2 ); + for ( byte value = Byte.MIN_VALUE; value < Byte.MAX_VALUE; value++ ) + { + Map handlersByListener = handlersByPriority.get( value ); + if ( handlersByListener != null ) + { + for ( Map.Entry listenerHandlers : handlersByListener.entrySet() ) + { + for ( Method method : listenerHandlers.getValue() ) + { + EventHandlerMethod ehm = new EventHandlerMethod( listenerHandlers.getKey(), method ); + handlersList.add( ehm ); + } + } + } + } + byEventBaked.put( eventClass, handlersList.toArray( new EventHandlerMethod[ handlersList.size() ] ) ); + } else + { + byEventBaked.put( eventClass, null ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/event/EventHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/event/EventHandler.java new file mode 100644 index 0000000..c8f1d78 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/event/EventHandler.java @@ -0,0 +1,26 @@ +package net.md_5.bungee.event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface EventHandler +{ + + /** + * Define the priority of the event handler. + *

+ * Event handlers are called in order of priority: + *

    + *
  1. LOWEST
  2. + *
  3. LOW
  4. + *
  5. NORMAL
  6. + *
  7. HIGH
  8. + *
  9. HIGHEST
  10. + *
+ */ + byte priority() default EventPriority.NORMAL; +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/event/EventHandlerMethod.java b/eaglerbungee/src/main/java/net/md_5/bungee/event/EventHandlerMethod.java new file mode 100644 index 0000000..5a78904 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/event/EventHandlerMethod.java @@ -0,0 +1,28 @@ +package net.md_5.bungee.event; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class EventHandlerMethod +{ + private final Object listener; + private final Method method; + + public Object getListener() { + return listener; + } + + public Method getMethod() { + return method; + } + + public EventHandlerMethod(Object listener, Method method) { + this.listener = listener; + this.method = method; + } + + public void invoke(Object event) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException + { + method.invoke( listener, event ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/event/EventPriority.java b/eaglerbungee/src/main/java/net/md_5/bungee/event/EventPriority.java new file mode 100644 index 0000000..b71be1f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/event/EventPriority.java @@ -0,0 +1,17 @@ +package net.md_5.bungee.event; + +/** + * Importance of the {@link EventHandler}. When executing an Event, the handlers + * are called in order of their Priority. + */ +public class EventPriority +{ + public EventPriority() { + } + + public static final byte LOWEST = -64; + public static final byte LOW = -32; + public static final byte NORMAL = 0; + public static final byte HIGH = 32; + public static final byte HIGHEST = 64; +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/log/BungeeLogger.java b/eaglerbungee/src/main/java/net/md_5/bungee/log/BungeeLogger.java new file mode 100644 index 0000000..fa8c508 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/log/BungeeLogger.java @@ -0,0 +1,48 @@ +package net.md_5.bungee.log; + +import java.io.IOException; +import java.util.logging.FileHandler; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import net.md_5.bungee.BungeeCord; + +public class BungeeLogger extends Logger +{ + + private final BungeeCord bungee; + private final ColouredWriter writer; + private final Formatter formatter = new ConciseFormatter(); + private final LogDispatcher dispatcher = new LogDispatcher( this ); + + public BungeeLogger(BungeeCord bungee) + { + super( "BungeeCord", null ); + this.bungee = bungee; + this.writer = new ColouredWriter( bungee.getConsoleReader() ); + + try + { + FileHandler handler = new FileHandler( "proxy.log", 1 << 24, 8, true ); + handler.setFormatter( formatter ); + addHandler( handler ); + } catch ( IOException ex ) + { + System.err.println( "Could not register logger!" ); + ex.printStackTrace(); + } + dispatcher.start(); + } + + @Override + public void log(LogRecord record) + { + dispatcher.queue( record ); + } + + void doLog(LogRecord record) + { + super.log( record ); + writer.print( formatter.format( record ) ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/log/ColouredWriter.java b/eaglerbungee/src/main/java/net/md_5/bungee/log/ColouredWriter.java new file mode 100644 index 0000000..9d2ead4 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/log/ColouredWriter.java @@ -0,0 +1,60 @@ +package net.md_5.bungee.log; + +import java.io.IOException; +import java.util.EnumMap; +import java.util.Map; +import jline.console.ConsoleReader; +import net.md_5.bungee.api.ChatColor; +import org.fusesource.jansi.Ansi; + +public class ColouredWriter +{ + + private final Map replacements = new EnumMap<>( ChatColor.class ); + private final ChatColor[] colors = ChatColor.values(); + private final ConsoleReader console; + + public ColouredWriter(ConsoleReader console) + { + this.console = console; + + replacements.put( ChatColor.BLACK, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLACK ).boldOff().toString() ); + replacements.put( ChatColor.DARK_BLUE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLUE ).boldOff().toString() ); + replacements.put( ChatColor.DARK_GREEN, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.GREEN ).boldOff().toString() ); + replacements.put( ChatColor.DARK_AQUA, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.CYAN ).boldOff().toString() ); + replacements.put( ChatColor.DARK_RED, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.RED ).boldOff().toString() ); + replacements.put( ChatColor.DARK_PURPLE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.MAGENTA ).boldOff().toString() ); + replacements.put( ChatColor.GOLD, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.YELLOW ).boldOff().toString() ); + replacements.put( ChatColor.GRAY, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.WHITE ).boldOff().toString() ); + replacements.put( ChatColor.DARK_GRAY, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLACK ).bold().toString() ); + replacements.put( ChatColor.BLUE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLUE ).bold().toString() ); + replacements.put( ChatColor.GREEN, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.GREEN ).bold().toString() ); + replacements.put( ChatColor.AQUA, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.CYAN ).bold().toString() ); + replacements.put( ChatColor.RED, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.RED ).bold().toString() ); + replacements.put( ChatColor.LIGHT_PURPLE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.MAGENTA ).bold().toString() ); + replacements.put( ChatColor.YELLOW, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.YELLOW ).bold().toString() ); + replacements.put( ChatColor.WHITE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.WHITE ).bold().toString() ); + replacements.put( ChatColor.MAGIC, Ansi.ansi().a( Ansi.Attribute.BLINK_SLOW ).toString() ); + replacements.put( ChatColor.BOLD, Ansi.ansi().a( Ansi.Attribute.UNDERLINE_DOUBLE ).toString() ); + replacements.put( ChatColor.STRIKETHROUGH, Ansi.ansi().a( Ansi.Attribute.STRIKETHROUGH_ON ).toString() ); + replacements.put( ChatColor.UNDERLINE, Ansi.ansi().a( Ansi.Attribute.UNDERLINE ).toString() ); + replacements.put( ChatColor.ITALIC, Ansi.ansi().a( Ansi.Attribute.ITALIC ).toString() ); + replacements.put( ChatColor.RESET, Ansi.ansi().a( Ansi.Attribute.RESET ).toString() ); + } + + public void print(String s) + { + for ( ChatColor color : colors ) + { + s = s.replaceAll( "(?i)" + color.toString(), replacements.get( color ) ); + } + try + { + console.print( ConsoleReader.RESET_LINE + s + Ansi.ansi().reset().toString() ); + console.drawLine(); + console.flush(); + } catch ( IOException ex ) + { + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/log/ConciseFormatter.java b/eaglerbungee/src/main/java/net/md_5/bungee/log/ConciseFormatter.java new file mode 100644 index 0000000..555e92a --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/log/ConciseFormatter.java @@ -0,0 +1,35 @@ +package net.md_5.bungee.log; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +public class ConciseFormatter extends Formatter +{ + + private final DateFormat date = new SimpleDateFormat( "HH:mm:ss" ); + + @Override + public String format(LogRecord record) + { + StringBuilder formatted = new StringBuilder(); + + formatted.append( date.format( record.getMillis() ) ); + formatted.append( " [" ); + formatted.append( record.getLevel().getLocalizedName() ); + formatted.append( "] " ); + formatted.append( formatMessage( record ) ); + formatted.append( '\n' ); + if ( record.getThrown() != null ) + { + StringWriter writer = new StringWriter(); + record.getThrown().printStackTrace( new PrintWriter( writer ) ); + formatted.append( writer ); + } + + return formatted.toString(); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/log/LogDispatcher.java b/eaglerbungee/src/main/java/net/md_5/bungee/log/LogDispatcher.java new file mode 100644 index 0000000..f1ccd4f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/log/LogDispatcher.java @@ -0,0 +1,48 @@ +package net.md_5.bungee.log; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.LogRecord; + +public class LogDispatcher extends Thread +{ + + private final BungeeLogger logger; + private final BlockingQueue queue = new LinkedBlockingQueue<>(); + + public LogDispatcher(BungeeLogger logger) + { + super( "BungeeCord Logger Thread" ); + this.logger = logger; + } + + @Override + public void run() + { + while ( !isInterrupted() ) + { + LogRecord record; + try + { + record = queue.take(); + } catch ( InterruptedException ex ) + { + continue; + } + + logger.doLog( record ); + } + for ( LogRecord record : queue ) + { + logger.doLog( record ); + } + } + + public void queue(LogRecord record) + { + if ( !isInterrupted() ) + { + queue.add( record ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/log/LoggingOutputStream.java b/eaglerbungee/src/main/java/net/md_5/bungee/log/LoggingOutputStream.java new file mode 100644 index 0000000..7970f57 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/log/LoggingOutputStream.java @@ -0,0 +1,36 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.log; + +import java.beans.ConstructorProperties; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class LoggingOutputStream extends ByteArrayOutputStream { + private static final String separator; + private final Logger logger; + private final Level level; + + @Override + public void flush() throws IOException { + final String contents = this.toString(); + super.reset(); + if (!contents.isEmpty() && !contents.equals(LoggingOutputStream.separator)) { + this.logger.logp(this.level, "", "", contents); + } + } + + @ConstructorProperties({ "logger", "level" }) + public LoggingOutputStream(final Logger logger, final Level level) { + this.logger = logger; + this.level = level; + } + + static { + separator = System.getProperty("line.separator"); + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java b/eaglerbungee/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java new file mode 100644 index 0000000..46fe8d5 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java @@ -0,0 +1,61 @@ +package net.md_5.bungee.netty; + +import com.google.common.base.Preconditions; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; + +public class ChannelWrapper +{ + + private final Channel ch; + + public boolean isClosed() { + return closed; + } + + private volatile boolean closed; + + public ChannelWrapper(ChannelHandlerContext ctx) + { + this.ch = ctx.channel(); + } + + public synchronized void write(Object packet) + { + if ( !closed ) + { + if ( packet instanceof PacketWrapper ) + { + ( (PacketWrapper) packet ).setReleased( true ); + ch.write( ( (PacketWrapper) packet ).buf ); + } else + { + ch.write( packet ); + } + //ch.flush(); + } + } + + public synchronized void close() + { + if ( !closed ) + { + closed = true; + //ch.flush(); + ch.close(); + } + } + + public void addBefore(String baseName, String name, ChannelHandler handler) + { + Preconditions.checkState( ch.eventLoop().inEventLoop(), "cannot add handler outside of event loop" ); + //ch.pipeline().flush(); + ch.pipeline().addBefore( baseName, name, handler ); + } + + public Channel getHandle() + { + return ch; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/netty/CipherBase.java b/eaglerbungee/src/main/java/net/md_5/bungee/netty/CipherBase.java new file mode 100644 index 0000000..ac74231 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/netty/CipherBase.java @@ -0,0 +1,67 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.netty; + +import java.beans.ConstructorProperties; + +import javax.crypto.Cipher; +import javax.crypto.ShortBufferException; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; + +public class CipherBase { + private final Cipher cipher; + private ThreadLocal heapInLocal; + private ThreadLocal heapOutLocal; + + private byte[] bufToByte(final ByteBuf in) { + byte[] heapIn = this.heapInLocal.get(); + final int readableBytes = in.readableBytes(); + if (heapIn.length < readableBytes) { + heapIn = new byte[readableBytes]; + this.heapInLocal.set(heapIn); + } + in.readBytes(heapIn, 0, readableBytes); + return heapIn; + } + + protected ByteBuf cipher(final ChannelHandlerContext ctx, final ByteBuf in) throws ShortBufferException { + final int readableBytes = in.readableBytes(); + final byte[] heapIn = this.bufToByte(in); + final ByteBuf heapOut = ctx.alloc().heapBuffer(this.cipher.getOutputSize(readableBytes)); + heapOut.writerIndex(this.cipher.update(heapIn, 0, readableBytes, heapOut.array(), heapOut.arrayOffset())); + return heapOut; + } + + protected void cipher(final ByteBuf in, final ByteBuf out) throws ShortBufferException { + final int readableBytes = in.readableBytes(); + final byte[] heapIn = this.bufToByte(in); + byte[] heapOut = this.heapOutLocal.get(); + final int outputSize = this.cipher.getOutputSize(readableBytes); + if (heapOut.length < outputSize) { + heapOut = new byte[outputSize]; + this.heapOutLocal.set(heapOut); + } + out.writeBytes(heapOut, 0, this.cipher.update(heapIn, 0, readableBytes, heapOut)); + } + + @ConstructorProperties({ "cipher" }) + protected CipherBase(final Cipher cipher) { + this.heapInLocal = new EmptyByteThreadLocal(); + this.heapOutLocal = new EmptyByteThreadLocal(); + if (cipher == null) { + throw new NullPointerException("cipher"); + } + this.cipher = cipher; + } + + private static class EmptyByteThreadLocal extends ThreadLocal { + @Override + protected byte[] initialValue() { + return new byte[0]; + } + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/netty/CipherDecoder.java b/eaglerbungee/src/main/java/net/md_5/bungee/netty/CipherDecoder.java new file mode 100644 index 0000000..1dab45c --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/netty/CipherDecoder.java @@ -0,0 +1,24 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.netty; + +import javax.crypto.Cipher; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.MessageList; +import io.netty.handler.codec.MessageToMessageDecoder; + +public class CipherDecoder extends MessageToMessageDecoder { + private final CipherBase cipher; + + public CipherDecoder(final Cipher cipher) { + this.cipher = new CipherBase(cipher); + } + + protected void decode(final ChannelHandlerContext ctx, final ByteBuf msg, final MessageList out) throws Exception { + out.add((Object) this.cipher.cipher(ctx, msg)); + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/netty/CipherEncoder.java b/eaglerbungee/src/main/java/net/md_5/bungee/netty/CipherEncoder.java new file mode 100644 index 0000000..7e5c0ec --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/netty/CipherEncoder.java @@ -0,0 +1,23 @@ +package net.md_5.bungee.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import javax.crypto.Cipher; + +public class CipherEncoder extends MessageToByteEncoder +{ + + private final CipherBase cipher; + + public CipherEncoder(Cipher cipher) + { + this.cipher = new CipherBase( cipher ); + } + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception + { + cipher.cipher( in, out ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/netty/DefinedPacketEncoder.java b/eaglerbungee/src/main/java/net/md_5/bungee/netty/DefinedPacketEncoder.java new file mode 100644 index 0000000..cfa3450 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/netty/DefinedPacketEncoder.java @@ -0,0 +1,19 @@ +package net.md_5.bungee.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import net.md_5.bungee.protocol.packet.DefinedPacket; + +@ChannelHandler.Sharable +public class DefinedPacketEncoder extends MessageToByteEncoder +{ + + @Override + protected void encode(ChannelHandlerContext ctx, DefinedPacket msg, ByteBuf out) throws Exception + { + out.writeByte( msg.getId() ); + msg.write( out ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/netty/HandlerBoss.java b/eaglerbungee/src/main/java/net/md_5/bungee/netty/HandlerBoss.java new file mode 100644 index 0000000..bf1a5d8 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/netty/HandlerBoss.java @@ -0,0 +1,124 @@ +package net.md_5.bungee.netty; + +import com.google.common.base.Preconditions; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.timeout.ReadTimeoutException; +import java.io.IOException; +import java.util.logging.Level; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.connection.CancelSendSignal; +import net.md_5.bungee.connection.InitialHandler; +import net.md_5.bungee.connection.PingHandler; +import net.md_5.bungee.protocol.BadPacketException; + +/** + * This class is a primitive wrapper for {@link PacketHandler} instances tied to + * channels to maintain simple states, and only call the required, adapted + * methods when the channel is connected. + */ +public class HandlerBoss extends ChannelInboundHandlerAdapter +{ + + private ChannelWrapper channel; + private PacketHandler handler; + + public void setHandler(PacketHandler handler) + { + Preconditions.checkArgument( handler != null, "handler" ); + this.handler = handler; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception + { + if ( handler != null ) + { + channel = new ChannelWrapper( ctx ); + handler.connected( channel ); + + if ( !( handler instanceof InitialHandler || handler instanceof PingHandler ) ) + { + ProxyServer.getInstance().getLogger().log( Level.INFO, "{0} has connected", handler ); + } + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception + { + if ( handler != null ) + { + handler.disconnected( channel ); + + if ( !( handler instanceof InitialHandler || handler instanceof PingHandler ) ) + { + ProxyServer.getInstance().getLogger().log( Level.INFO, "{0} has disconnected", handler ); + } + } + } + + //@Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception + { + if ( handler != null ) + { + PacketWrapper packet = (PacketWrapper) msg; + boolean sendPacket = true; + try + { + if ( packet.packet != null ) + { + try + { + packet.packet.handle( handler ); + } catch ( CancelSendSignal ex ) + { + sendPacket = false; + } + } + if ( sendPacket ) + { + handler.handle( packet ); + } + } finally + { + packet.trySingleRelease(); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception + { + if ( ctx.channel().isActive() ) + { + if ( cause instanceof ReadTimeoutException ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, handler + " - read timed out" ); + } else if ( cause instanceof BadPacketException ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, handler + " - bad packet ID, are mods in use!?" ); + } else if ( cause instanceof IOException ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, handler + " - IOException: " + cause.getMessage() ); + } else + { + ProxyServer.getInstance().getLogger().log( Level.SEVERE, handler + " - encountered exception", cause ); + } + + if ( handler != null ) + { + try + { + handler.exception( cause ); + } catch ( Exception ex ) + { + ProxyServer.getInstance().getLogger().log( Level.SEVERE, handler + " - exception processing exception", ex ); + } + } + + ctx.close(); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/netty/PacketDecoder.java b/eaglerbungee/src/main/java/net/md_5/bungee/netty/PacketDecoder.java new file mode 100644 index 0000000..7b17881 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/netty/PacketDecoder.java @@ -0,0 +1,50 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.netty; + +import java.beans.ConstructorProperties; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.MessageList; +import io.netty.handler.codec.ReplayingDecoder; +import net.md_5.bungee.protocol.Protocol; +import net.md_5.bungee.protocol.packet.DefinedPacket; + +public class PacketDecoder extends ReplayingDecoder { + private Protocol protocol; + + protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final MessageList out) throws Exception { + while (true) { + final int startIndex = in.readerIndex(); + final DefinedPacket packet = this.protocol.read(in.readUnsignedByte(), in); + final int endIndex = in.readerIndex(); + final byte[] buf = new byte[endIndex - startIndex]; + in.readerIndex(startIndex); + in.readBytes(buf, 0, buf.length); + in.readerIndex(endIndex); + this.checkpoint(); + if (packet != null) { + out.add((Object) new PacketWrapper(packet, Unpooled.wrappedBuffer(buf))); + } else { + out.add((Object) buf); + } + } + } + + @ConstructorProperties({ "protocol" }) + public PacketDecoder(final Protocol protocol) { + this.protocol = protocol; + } + + public Protocol getProtocol() { + return this.protocol; + } + + public void setProtocol(final Protocol protocol) { + this.protocol = protocol; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/netty/PacketHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/netty/PacketHandler.java new file mode 100644 index 0000000..ca4684f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/netty/PacketHandler.java @@ -0,0 +1,24 @@ +package net.md_5.bungee.netty; + +public abstract class PacketHandler extends net.md_5.bungee.protocol.packet.AbstractPacketHandler +{ + + @Override + public abstract String toString(); + + public void exception(Throwable t) throws Exception + { + } + + public void handle(PacketWrapper packet) throws Exception + { + } + + public void connected(ChannelWrapper channel) throws Exception + { + } + + public void disconnected(ChannelWrapper channel) throws Exception + { + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/netty/PacketWrapper.java b/eaglerbungee/src/main/java/net/md_5/bungee/netty/PacketWrapper.java new file mode 100644 index 0000000..066bff2 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/netty/PacketWrapper.java @@ -0,0 +1,31 @@ +package net.md_5.bungee.netty; + +import io.netty.buffer.ByteBuf; +import net.md_5.bungee.protocol.packet.DefinedPacket; + +public class PacketWrapper +{ + + public final DefinedPacket packet; + public final ByteBuf buf; + + public PacketWrapper(DefinedPacket packet, ByteBuf buf) { + this.packet = packet; + this.buf = buf; + } + + public void setReleased(boolean released) { + this.released = released; + } + + private boolean released; + + public void trySingleRelease() + { + if ( !released ) + { + buf.release(); + released = true; + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/netty/PipelineUtils.java b/eaglerbungee/src/main/java/net/md_5/bungee/netty/PipelineUtils.java new file mode 100644 index 0000000..235a91e --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/netty/PipelineUtils.java @@ -0,0 +1,70 @@ +package net.md_5.bungee.netty; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.util.AttributeKey; +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.BungeeServerInfo; +import net.md_5.bungee.ServerConnector; +import net.md_5.bungee.UserConnection; +import net.md_5.bungee.connection.InitialHandler; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.protocol.Vanilla; + +public class PipelineUtils +{ + + public static final AttributeKey LISTENER = new AttributeKey<>( "ListerInfo" ); + public static final AttributeKey USER = new AttributeKey<>( "User" ); + public static final AttributeKey TARGET = new AttributeKey<>( "Target" ); + public static final ChannelInitializer SERVER_CHILD = new ChannelInitializer() { + protected void initChannel(final Channel ch) throws Exception { + PipelineUtils.BASE.initChannel(ch); + ((HandlerBoss) ch.pipeline().get((Class) HandlerBoss.class)).setHandler(new InitialHandler(ProxyServer.getInstance(), (ListenerInfo) ch.attr((AttributeKey) PipelineUtils.LISTENER).get())); + } + }; + public static final ChannelInitializer CLIENT = new ChannelInitializer() + { + @Override + protected void initChannel(Channel ch) throws Exception + { + BASE.initChannel( ch ); + ch.pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( ProxyServer.getInstance(), ch.attr( USER ).get(), ch.attr( TARGET ).get() ) ); + } + }; + public static final Base BASE = new Base(); + private static final DefinedPacketEncoder packetEncoder = new DefinedPacketEncoder(); + public static String TIMEOUT_HANDLER = "timeout"; + public static String PACKET_DECODE_HANDLER = "packet-decoder"; + public static String PACKET_ENCODE_HANDLER = "packet-encoder"; + public static String BOSS_HANDLER = "inbound-boss"; + public static String ENCRYPT_HANDLER = "encrypt"; + public static String DECRYPT_HANDLER = "decrypt"; + + public final static class Base extends ChannelInitializer + { + + @Override + public void initChannel(Channel ch) throws Exception + { + try + { + ch.config().setOption( ChannelOption.IP_TOS, 0x18 ); + } catch ( ChannelException ex ) + { + // IP_TOS is not supported (Windows XP / Windows Server 2003) + } + + ch.pipeline().addLast( TIMEOUT_HANDLER, new ReadTimeoutHandler( BungeeCord.getInstance().config.getTimeout(), TimeUnit.MILLISECONDS ) ); + ch.pipeline().addLast( PACKET_DECODE_HANDLER, new PacketDecoder( Vanilla.getInstance() ) ); + ch.pipeline().addLast( PACKET_ENCODE_HANDLER, packetEncoder ); + ch.pipeline().addLast( BOSS_HANDLER, new HandlerBoss() ); + } + }; +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/BadPacketException.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/BadPacketException.java new file mode 100644 index 0000000..523aeaf --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/BadPacketException.java @@ -0,0 +1,10 @@ +package net.md_5.bungee.protocol; + +public class BadPacketException extends RuntimeException +{ + + public BadPacketException(String message) + { + super( message ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/Forge.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/Forge.java new file mode 100644 index 0000000..16ccb94 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/Forge.java @@ -0,0 +1,35 @@ +package net.md_5.bungee.protocol; + +import io.netty.buffer.ByteBuf; +import net.md_5.bungee.protocol.packet.DefinedPacket; +import net.md_5.bungee.protocol.packet.forge.Forge1Login; +import net.md_5.bungee.protocol.skip.PacketReader; + +public class Forge extends Vanilla +{ + + private static final Forge instance = new Forge(); + + public static Forge getInstance(){ + return instance; + } + + public Forge() + { + classes[0x01] = Forge1Login.class; + skipper = new PacketReader( this ); // TODO: :( + } + + @Override + public DefinedPacket read(short packetId, ByteBuf buf) + { + int start = buf.readerIndex(); + DefinedPacket packet = read( packetId, buf, this ); + if ( buf.readerIndex() == start ) + { + packet = super.read( packetId, buf ); + } + + return packet; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/MinecraftInput.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/MinecraftInput.java new file mode 100644 index 0000000..fb0495a --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/MinecraftInput.java @@ -0,0 +1,40 @@ +package net.md_5.bungee.protocol; + +import io.netty.buffer.ByteBuf; + +public class MinecraftInput +{ + + public MinecraftInput(ByteBuf buf) { + this.buf = buf; + } + + private final ByteBuf buf; + + public byte readByte() + { + return buf.readByte(); + } + + public short readUnisgnedByte() + { + return buf.readUnsignedByte(); + } + + public int readInt() + { + return buf.readInt(); + } + + public String readString() + { + short len = buf.readShort(); + char[] c = new char[ len ]; + for ( int i = 0; i < c.length; i++ ) + { + c[i] = buf.readChar(); + } + + return new String( c ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/MinecraftOutput.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/MinecraftOutput.java new file mode 100644 index 0000000..94f6613 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/MinecraftOutput.java @@ -0,0 +1,56 @@ +package net.md_5.bungee.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.nio.charset.Charset; +import java.util.Arrays; + +public class MinecraftOutput +{ + + private final ByteBuf buf; + + public MinecraftOutput() + { + buf = Unpooled.buffer(); + } + + public byte[] toArray() + { + if ( buf.hasArray() ) + { + return Arrays.copyOfRange( buf.array(), buf.arrayOffset(), buf.arrayOffset() + buf.writerIndex() ); + } else + { + byte[] b = new byte[ buf.writerIndex() ]; + buf.readBytes( b ); + return b; + } + } + + public MinecraftOutput writeByte(byte b) + { + buf.writeByte( b ); + return this; + } + + public void writeInt(int i) + { + buf.writeInt( i ); + } + + public void writeString(String s) + { + char[] cc = s.toCharArray(); + buf.writeShort( cc.length ); + for ( char c : cc ) + { + buf.writeChar( c ); + } + } + + public void writeStringUTF8WithoutLengthHeaderBecauseDinnerboneStuffedUpTheMCBrandPacket(String s) + { + buf.writeBytes( s.getBytes( Charset.forName( "UTF-8" ) ) ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/OpCode.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/OpCode.java new file mode 100644 index 0000000..e201c5c --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/OpCode.java @@ -0,0 +1,7 @@ +package net.md_5.bungee.protocol; + +public enum OpCode +{ + + BOOLEAN, BULK_CHUNK, BYTE, BYTE_INT, DOUBLE, FLOAT, INT, INT_3, INT_BYTE, ITEM, LONG, METADATA, OPTIONAL_MOTION, SHORT, SHORT_BYTE, SHORT_ITEM, STRING, USHORT_BYTE, OPTIONAL_WINDOW +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/Protocol.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/Protocol.java new file mode 100644 index 0000000..e88d8f5 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/Protocol.java @@ -0,0 +1,21 @@ +package net.md_5.bungee.protocol; + + +import io.netty.buffer.ByteBuf; +import java.lang.reflect.Constructor; +import net.md_5.bungee.protocol.packet.DefinedPacket; +import net.md_5.bungee.protocol.skip.PacketReader; + +public interface Protocol +{ + + PacketReader getSkipper(); + + DefinedPacket read(short packetId, ByteBuf buf); + + OpCode[][] getOpCodes(); + + Class[] getClasses(); + + Constructor[] getConstructors(); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/Vanilla.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/Vanilla.java new file mode 100644 index 0000000..86dbc63 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/Vanilla.java @@ -0,0 +1,408 @@ +package net.md_5.bungee.protocol; + +import io.netty.buffer.ByteBuf; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import static net.md_5.bungee.protocol.OpCode.*; +import net.md_5.bungee.protocol.packet.DefinedPacket; +import net.md_5.bungee.protocol.packet.Packet0KeepAlive; +import net.md_5.bungee.protocol.packet.Packet1Login; +import net.md_5.bungee.protocol.packet.Packet2CEntityProperties; +import net.md_5.bungee.protocol.packet.Packet2Handshake; +import net.md_5.bungee.protocol.packet.Packet3Chat; +import net.md_5.bungee.protocol.packet.Packet9Respawn; +import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem; +import net.md_5.bungee.protocol.packet.PacketCBTabComplete; +import net.md_5.bungee.protocol.packet.PacketCCSettings; +import net.md_5.bungee.protocol.packet.PacketCDClientStatus; +import net.md_5.bungee.protocol.packet.PacketCEScoreboardObjective; +import net.md_5.bungee.protocol.packet.PacketCFScoreboardScore; +import net.md_5.bungee.protocol.packet.PacketD0DisplayScoreboard; +import net.md_5.bungee.protocol.packet.PacketD1Team; +import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; +import net.md_5.bungee.protocol.packet.PacketFCEncryptionResponse; +import net.md_5.bungee.protocol.packet.PacketFDEncryptionRequest; +import net.md_5.bungee.protocol.packet.PacketFEPing; +import net.md_5.bungee.protocol.packet.PacketFFKick; +import net.md_5.bungee.protocol.skip.PacketReader; + +public class Vanilla implements Protocol +{ + + public static final byte PROTOCOL_VERSION = 78; + public static final String GAME_VERSION = "1.6.4"; + private static Vanilla instance = new Vanilla(); + /*========================================================================*/ + private final OpCode[][] opCodes = new OpCode[ 256 ][]; + @SuppressWarnings("unchecked") + protected Class[] classes = new Class[ 256 ]; + @SuppressWarnings("unchecked") + private Constructor[] constructors = new Constructor[ 256 ]; + protected PacketReader skipper; + /*========================================================================*/ + + public Vanilla() + { + classes[0x00] = Packet0KeepAlive.class; + classes[0x01] = Packet1Login.class; + classes[0x02] = Packet2Handshake.class; + classes[0x03] = Packet3Chat.class; + classes[0x09] = Packet9Respawn.class; + classes[0xC9] = PacketC9PlayerListItem.class; + classes[0x2C] = Packet2CEntityProperties.class; + classes[0xCC] = PacketCCSettings.class; + classes[0xCB] = PacketCBTabComplete.class; + classes[0xCD] = PacketCDClientStatus.class; + classes[0xCE] = PacketCEScoreboardObjective.class; + classes[0xCF] = PacketCFScoreboardScore.class; + classes[0xD0] = PacketD0DisplayScoreboard.class; + classes[0xD1] = PacketD1Team.class; + classes[0xFA] = PacketFAPluginMessage.class; + classes[0xFC] = PacketFCEncryptionResponse.class; + classes[0xFD] = PacketFDEncryptionRequest.class; + classes[0xFE] = PacketFEPing.class; + classes[0xFF] = PacketFFKick.class; + skipper = new PacketReader( this ); + } + + @Override + public DefinedPacket read(short packetId, ByteBuf buf) + { + int start = buf.readerIndex(); + DefinedPacket packet = read( packetId, buf, this ); + if ( buf.readerIndex() == start ) + { + throw new BadPacketException( "Unknown packet id " + packetId ); + } + return packet; + } + + public static DefinedPacket read(short id, ByteBuf buf, Protocol protocol) + { + DefinedPacket packet = packet( id, protocol ); + if ( packet != null ) + { + packet.read( buf ); + return packet; + } + protocol.getSkipper().tryRead( id, buf ); + return null; + } + + public static DefinedPacket packet(short id, Protocol protocol) + { + DefinedPacket ret = null; + Class clazz = protocol.getClasses()[id]; + + if ( clazz != null ) + { + try + { + Constructor constructor = protocol.getConstructors()[id]; + if ( constructor == null ) + { + constructor = clazz.getDeclaredConstructor(); + constructor.setAccessible( true ); + protocol.getConstructors()[id] = constructor; + } + + if ( constructor != null ) + { + ret = constructor.newInstance(); + } + } catch ( NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex ) + { + } + } + + return ret; + } + + + { + opCodes[0x04] = new OpCode[] + { + LONG, LONG + }; + opCodes[0x05] = new OpCode[] + { + INT, SHORT, ITEM + }; + opCodes[0x06] = new OpCode[] + { + INT, INT, INT + }; + opCodes[0x07] = new OpCode[] + { + INT, INT, BOOLEAN + }; + opCodes[0x08] = new OpCode[] + { + FLOAT, SHORT, FLOAT + }; + opCodes[0x0A] = new OpCode[] + { + BOOLEAN + }; + opCodes[0x0B] = new OpCode[] + { + DOUBLE, DOUBLE, DOUBLE, DOUBLE, BOOLEAN + }; + opCodes[0x0C] = new OpCode[] + { + FLOAT, FLOAT, BOOLEAN + }; + opCodes[0x0D] = new OpCode[] + { + DOUBLE, DOUBLE, DOUBLE, DOUBLE, FLOAT, FLOAT, BOOLEAN + }; + opCodes[0x0E] = new OpCode[] + { + BYTE, INT, BYTE, INT, BYTE + }; + opCodes[0x0F] = new OpCode[] + { + INT, BYTE, INT, BYTE, ITEM, BYTE, BYTE, BYTE + }; + opCodes[0x10] = new OpCode[] + { + SHORT + }; + opCodes[0x11] = new OpCode[] + { + INT, BYTE, INT, BYTE, INT + }; + opCodes[0x12] = new OpCode[] + { + INT, BYTE + }; + opCodes[0x13] = new OpCode[] + { + INT, BYTE, INT + }; + opCodes[0x14] = new OpCode[] + { + INT, STRING, INT, INT, INT, BYTE, BYTE, SHORT, METADATA + }; + opCodes[0x16] = new OpCode[] + { + INT, INT + }; + opCodes[0x17] = new OpCode[] + { + INT, BYTE, INT, INT, INT, BYTE, BYTE, OPTIONAL_MOTION + }; + opCodes[0x18] = new OpCode[] + { + INT, BYTE, INT, INT, INT, BYTE, BYTE, BYTE, SHORT, SHORT, SHORT, METADATA + }; + opCodes[0x19] = new OpCode[] + { + INT, STRING, INT, INT, INT, INT + }; + opCodes[0x1A] = new OpCode[] + { + INT, INT, INT, INT, SHORT + }; + opCodes[0x1B] = new OpCode[] + { + FLOAT, FLOAT, BOOLEAN, BOOLEAN + }; + opCodes[0x1C] = new OpCode[] + { + INT, SHORT, SHORT, SHORT + }; + opCodes[0x1D] = new OpCode[] + { + BYTE_INT + }; + opCodes[0x1E] = new OpCode[] + { + INT + }; + opCodes[0x1F] = new OpCode[] + { + INT, BYTE, BYTE, BYTE + }; + opCodes[0x20] = new OpCode[] + { + INT, BYTE, BYTE + }; + opCodes[0x21] = new OpCode[] + { + INT, BYTE, BYTE, BYTE, BYTE, BYTE + }; + opCodes[0x22] = new OpCode[] + { + INT, INT, INT, INT, BYTE, BYTE + }; + opCodes[0x23] = new OpCode[] + { + INT, BYTE + }; + opCodes[0x26] = new OpCode[] + { + INT, BYTE + }; + opCodes[0x27] = new OpCode[] + { + INT, INT, BOOLEAN + }; + opCodes[0x28] = new OpCode[] + { + INT, METADATA + }; + opCodes[0x29] = new OpCode[] + { + INT, BYTE, BYTE, SHORT + }; + opCodes[0x2A] = new OpCode[] + { + INT, BYTE + }; + opCodes[0x2B] = new OpCode[] + { + FLOAT, SHORT, SHORT + }; + opCodes[0x33] = new OpCode[] + { + INT, INT, BOOLEAN, SHORT, SHORT, INT_BYTE + }; + opCodes[0x34] = new OpCode[] + { + INT, INT, SHORT, INT_BYTE + }; + opCodes[0x35] = new OpCode[] + { + INT, BYTE, INT, SHORT, BYTE + }; + opCodes[0x36] = new OpCode[] + { + INT, SHORT, INT, BYTE, BYTE, SHORT + }; + opCodes[0x37] = new OpCode[] + { + INT, INT, INT, INT, BYTE + }; + opCodes[0x38] = new OpCode[] + { + BULK_CHUNK + }; + opCodes[0x3C] = new OpCode[] + { + DOUBLE, DOUBLE, DOUBLE, FLOAT, INT_3, FLOAT, FLOAT, FLOAT + }; + opCodes[0x3D] = new OpCode[] + { + INT, INT, BYTE, INT, INT, BOOLEAN + }; + opCodes[0x3E] = new OpCode[] + { + STRING, INT, INT, INT, FLOAT, BYTE + }; + opCodes[0x3F] = new OpCode[] + { + STRING, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, INT + }; + opCodes[0x46] = new OpCode[] + { + BYTE, BYTE + }; + opCodes[0x47] = new OpCode[] + { + INT, BYTE, INT, INT, INT + }; + opCodes[0x64] = new OpCode[] + { + OPTIONAL_WINDOW + }; + opCodes[0x65] = new OpCode[] + { + BYTE + }; + opCodes[0x66] = new OpCode[] + { + BYTE, SHORT, BYTE, SHORT, BOOLEAN, ITEM + }; + opCodes[0x67] = new OpCode[] + { + BYTE, SHORT, ITEM + }; + opCodes[0x68] = new OpCode[] + { + BYTE, SHORT_ITEM + }; + opCodes[0x69] = new OpCode[] + { + BYTE, SHORT, SHORT + }; + opCodes[0x6A] = new OpCode[] + { + BYTE, SHORT, BOOLEAN + }; + opCodes[0x6B] = new OpCode[] + { + SHORT, ITEM + }; + opCodes[0x6C] = new OpCode[] + { + BYTE, BYTE + }; + opCodes[0x82] = new OpCode[] + { + INT, SHORT, INT, STRING, STRING, STRING, STRING + }; + opCodes[0x83] = new OpCode[] + { + SHORT, SHORT, USHORT_BYTE + }; + opCodes[0x84] = new OpCode[] + { + INT, SHORT, INT, BYTE, SHORT_BYTE + }; + opCodes[0x85] = new OpCode[] + { + BYTE, INT, INT, INT + }; + opCodes[0xC3] = new OpCode[] + { + SHORT, SHORT, INT_BYTE + }; + opCodes[0xC8] = new OpCode[] + { + INT, INT + }; + opCodes[0xCA] = new OpCode[] + { + BYTE, FLOAT, FLOAT + }; + } + + public static Vanilla getInstance() { + return Vanilla.instance; + } + + @Override + public OpCode[][] getOpCodes() { + return this.opCodes; + } + + @Override + public Class[] getClasses() { + return this.classes; + } + + @Override + public Constructor[] getConstructors() { + return this.constructors; + } + + @Override + public PacketReader getSkipper() { + return this.skipper; + } + + static { + instance = new Vanilla(); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/AbstractPacketHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/AbstractPacketHandler.java new file mode 100644 index 0000000..0496a64 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/AbstractPacketHandler.java @@ -0,0 +1,85 @@ +package net.md_5.bungee.protocol.packet; + +public abstract class AbstractPacketHandler +{ + + public void handle(Packet0KeepAlive alive) throws Exception + { + } + + public void handle(Packet0DPositionAndLook packet) throws Exception + { + } + + public void handle(Packet1Login login) throws Exception + { + } + + public void handle(Packet2Handshake handshake) throws Exception + { + } + + public void handle(Packet3Chat chat) throws Exception + { + } + + public void handle(Packet9Respawn respawn) throws Exception + { + } + + public void handle(Packet2CEntityProperties properties) throws Exception + { + } + + public void handle(PacketC9PlayerListItem playerList) throws Exception + { + } + + public void handle(PacketCCSettings settings) throws Exception + { + } + + public void handle(PacketCDClientStatus clientStatus) throws Exception + { + } + + public void handle(PacketCEScoreboardObjective objective) throws Exception + { + } + + public void handle(PacketCFScoreboardScore score) throws Exception + { + } + + public void handle(PacketD0DisplayScoreboard displayScoreboard) throws Exception + { + } + + public void handle(PacketD1Team team) throws Exception + { + } + + public void handle(PacketFAPluginMessage pluginMessage) throws Exception + { + } + + public void handle(PacketFCEncryptionResponse encryptResponse) throws Exception + { + } + + public void handle(PacketFDEncryptionRequest encryptRequest) throws Exception + { + } + + public void handle(PacketFEPing ping) throws Exception + { + } + + public void handle(PacketFFKick kick) throws Exception + { + } + + public void handle(PacketCBTabComplete tabComplete) throws Exception + { + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/DefinedPacket.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/DefinedPacket.java new file mode 100644 index 0000000..4ab0121 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/DefinedPacket.java @@ -0,0 +1,71 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public abstract class DefinedPacket +{ + + private final int id; + + public final int getId() + { + return id; + } + + public DefinedPacket(int id){ + this.id = id; + } + + public void writeString(String s, ByteBuf buf) + { + // TODO: Check len - use Guava? + buf.writeShort( s.length() ); + for ( char c : s.toCharArray() ) + { + buf.writeChar( c ); + } + } + + public String readString(ByteBuf buf) + { + // TODO: Check len - use Guava? + short len = buf.readShort(); + char[] chars = new char[ len ]; + for ( int i = 0; i < len; i++ ) + { + chars[i] = buf.readChar(); + } + return new String( chars ); + } + + public void writeArray(byte[] b, ByteBuf buf) + { + // TODO: Check len - use Guava? + buf.writeShort( b.length ); + buf.writeBytes( b ); + } + + public byte[] readArray(ByteBuf buf) + { + // TODO: Check len - use Guava? + short len = buf.readShort(); + byte[] ret = new byte[ len ]; + buf.readBytes( ret ); + return ret; + } + + public abstract void read(ByteBuf buf); + + public abstract void write(ByteBuf buf); + + public abstract void handle(AbstractPacketHandler handler) throws Exception; + + @Override + public abstract boolean equals(Object obj); + + @Override + public abstract int hashCode(); + + @Override + public abstract String toString(); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet0DPositionAndLook.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet0DPositionAndLook.java new file mode 100644 index 0000000..ac284ad --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet0DPositionAndLook.java @@ -0,0 +1,115 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class Packet0DPositionAndLook extends DefinedPacket { + private double x; + private double y; + private double stance; + private double z; + private float yaw; + private float pitch; + private boolean onGround; + + private Packet0DPositionAndLook() { + super(13); + } + + public Packet0DPositionAndLook(final int x, final int y, final double stance, final double z, final float yaw, + final float pitch, final boolean onGround) { + this(); + this.x = x; + this.y = y; + this.stance = stance; + this.z = z; + this.yaw = yaw; + this.pitch = pitch; + this.onGround = onGround; + } + + @Override + public void read(final ByteBuf buf) { + this.x = buf.readDouble(); + this.y = buf.readDouble(); + this.stance = buf.readDouble(); + this.z = buf.readDouble(); + this.yaw = buf.readFloat(); + this.pitch = buf.readFloat(); + this.onGround = buf.readBoolean(); + } + + @Override + public void write(final ByteBuf buf) { + buf.writeDouble(this.x); + buf.writeDouble(this.y); + buf.writeDouble(this.stance); + buf.writeDouble(this.z); + buf.writeFloat(this.yaw); + buf.writeFloat(this.pitch); + buf.writeBoolean(this.onGround); + } + + @Override + public void handle(final AbstractPacketHandler handler) throws Exception { + handler.handle(this); + } + + @Override + public String toString() { + return "Packet0DPositionAndLook(tooLazyToFillThisInLOL)"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Packet0DPositionAndLook)) { + return false; + } + final Packet0DPositionAndLook other = (Packet0DPositionAndLook) o; + if (!other.canEqual(this)) { + return false; + } + if (this.x != other.x) { + return false; + } + if (this.y != other.y) { + return false; + } + if (this.stance != other.stance) { + return false; + } + if (this.z != other.z) { + return false; + } + if (this.yaw != other.yaw) { + return false; + } + if (this.pitch != other.pitch) { + return false; + } + if (this.onGround != other.onGround) { + return false; + } + return false; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + Double.hashCode(this.x); + result = result * 31 + Double.hashCode(this.y); + result = result * 31 + Double.hashCode(this.stance); + result = result * 31 + Double.hashCode(this.z); + result = result * 31 + Float.hashCode(this.yaw); + result = result * 31 + Float.hashCode(this.pitch); + result = result * 31 + Boolean.hashCode(this.onGround); + return result; + } + + public boolean canEqual(final Object other) { + return other instanceof Packet0DPositionAndLook; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet0KeepAlive.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet0KeepAlive.java new file mode 100644 index 0000000..10c7f44 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet0KeepAlive.java @@ -0,0 +1,65 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class Packet0KeepAlive extends DefinedPacket +{ + + private int randomId; + + private Packet0KeepAlive() + { + super( 0x00 ); + } + + @Override + public void read(ByteBuf buf) + { + randomId = buf.readInt(); + } + + @Override + public void write(ByteBuf buf) + { + buf.writeInt( randomId ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + public int getRandomId() { + return this.randomId; + } + + @Override + public String toString() { + return "Packet0KeepAlive(randomId=" + this.getRandomId() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Packet0KeepAlive)) { + return false; + } + final Packet0KeepAlive other = (Packet0KeepAlive) o; + return other.canEqual(this) && this.getRandomId() == other.getRandomId(); + } + + public boolean canEqual(final Object other) { + return other instanceof Packet0KeepAlive; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + this.getRandomId(); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet1Login.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet1Login.java new file mode 100644 index 0000000..f3eca76 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet1Login.java @@ -0,0 +1,149 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class Packet1Login extends DefinedPacket +{ + + protected int entityId; + protected String levelType; + protected byte gameMode; + protected int dimension; + protected byte difficulty; + protected byte unused; + protected byte maxPlayers; + + protected Packet1Login() + { + super( 0x01 ); + } + + public Packet1Login(int entityId, String levelType, byte gameMode, byte dimension, byte difficulty, byte unused, byte maxPlayers) + { + this( entityId, levelType, gameMode, (int) dimension, difficulty, unused, maxPlayers ); + } + + public Packet1Login(int entityId, String levelType, byte gameMode, int dimension, byte difficulty, byte unused, byte maxPlayers) + { + this(); + this.entityId = entityId; + this.levelType = levelType; + this.gameMode = gameMode; + this.dimension = dimension; + this.difficulty = difficulty; + this.unused = unused; + this.maxPlayers = maxPlayers; + } + + @Override + public void read(ByteBuf buf) + { + entityId = buf.readInt(); + levelType = readString( buf ); + gameMode = buf.readByte(); + dimension = buf.readByte(); + difficulty = buf.readByte(); + unused = buf.readByte(); + maxPlayers = buf.readByte(); + } + + @Override + public void write(ByteBuf buf) + { + buf.writeInt( entityId ); + writeString( levelType, buf ); + buf.writeByte( gameMode ); + buf.writeByte( dimension ); + buf.writeByte( difficulty ); + buf.writeByte( unused ); + buf.writeByte( maxPlayers ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + public int getEntityId() { + return this.entityId; + } + + public String getLevelType() { + return this.levelType; + } + + public byte getGameMode() { + return this.gameMode; + } + + public int getDimension() { + return this.dimension; + } + + public byte getDifficulty() { + return this.difficulty; + } + + public byte getUnused() { + return this.unused; + } + + public byte getMaxPlayers() { + return this.maxPlayers; + } + + @Override + public String toString() { + return "Packet1Login(entityId=" + this.getEntityId() + ", levelType=" + this.getLevelType() + ", gameMode=" + this.getGameMode() + ", dimension=" + this.getDimension() + ", difficulty=" + this.getDifficulty() + ", unused=" + + this.getUnused() + ", maxPlayers=" + this.getMaxPlayers() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Packet1Login)) { + return false; + } + final Packet1Login other = (Packet1Login) o; + if (!other.canEqual(this)) { + return false; + } + if (this.getEntityId() != other.getEntityId()) { + return false; + } + final Object this$levelType = this.getLevelType(); + final Object other$levelType = other.getLevelType(); + if (this$levelType == null) { + if (other$levelType == null) { + return this.getGameMode() == other.getGameMode() && this.getDimension() == other.getDimension() && this.getDifficulty() == other.getDifficulty() && this.getUnused() == other.getUnused() + && this.getMaxPlayers() == other.getMaxPlayers(); + } + } else if (this$levelType.equals(other$levelType)) { + return this.getGameMode() == other.getGameMode() && this.getDimension() == other.getDimension() && this.getDifficulty() == other.getDifficulty() && this.getUnused() == other.getUnused() + && this.getMaxPlayers() == other.getMaxPlayers(); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof Packet1Login; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + this.getEntityId(); + final Object $levelType = this.getLevelType(); + result = result * 31 + (($levelType == null) ? 0 : $levelType.hashCode()); + result = result * 31 + this.getGameMode(); + result = result * 31 + this.getDimension(); + result = result * 31 + this.getDifficulty(); + result = result * 31 + this.getUnused(); + result = result * 31 + this.getMaxPlayers(); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet2CEntityProperties.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet2CEntityProperties.java new file mode 100644 index 0000000..b65fcaa --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet2CEntityProperties.java @@ -0,0 +1,68 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class Packet2CEntityProperties extends DefinedPacket +{ + + public Packet2CEntityProperties() + { + super( 0x2C ); + } + + @Override + public String toString() { + return "Packet2CEntityProperties{}"; + } + + @Override + public void read(ByteBuf buf) + { + buf.readInt(); + int recordCount = buf.readInt(); + for ( int i = 0; i < recordCount; i++ ) + { + readString( buf ); + buf.readDouble(); + short size = buf.readShort(); + for ( short s = 0; s < size; s++ ) + { + buf.skipBytes( 25 ); // long, long, double, byte + } + } + } + + @Override + public void write(ByteBuf buf) + { + throw new UnsupportedOperationException(); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + else if(!(o instanceof PacketCEScoreboardObjective)) { + return false; + } + else { + return false; + } + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.hashCode(); + result = result * 31 + (($name == null) ? 0 : $name.hashCode()); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet2Handshake.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet2Handshake.java new file mode 100644 index 0000000..d4bba2e --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet2Handshake.java @@ -0,0 +1,128 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class Packet2Handshake extends DefinedPacket { + private byte procolVersion; + private String username; + private String host; + private int port; + + private Packet2Handshake() { + super(2); + } + + @Override + public void read(final ByteBuf buf) { + this.procolVersion = buf.readByte(); + this.username = this.readString(buf); + this.host = this.readString(buf); + this.port = buf.readInt(); + } + + @Override + public void write(final ByteBuf buf) { + buf.writeByte((int) this.procolVersion); + this.writeString(this.username, buf); + this.writeString(this.host, buf); + buf.writeInt(this.port); + } + + @Override + public void handle(final AbstractPacketHandler handler) throws Exception { + handler.handle(this); + } + + public byte getProcolVersion() { + return this.procolVersion; + } + + public void swapProtocol(byte b) { + this.procolVersion = (byte)b; + } + + public void setHost(String h) { + this.host = h; + } + + public void setPort(int p) { + this.port = p; + } + + public String getUsername() { + return this.username; + } + + public String getHost() { + return this.host; + } + + public int getPort() { + return this.port; + } + + @Override + public String toString() { + return "Packet2Handshake(procolVersion=" + this.getProcolVersion() + ", username=" + this.getUsername() + ", host=" + this.getHost() + ", port=" + this.getPort() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Packet2Handshake)) { + return false; + } + final Packet2Handshake other = (Packet2Handshake) o; + if (!other.canEqual(this)) { + return false; + } + if (this.getProcolVersion() != other.getProcolVersion()) { + return false; + } + final Object this$username = this.getUsername(); + final Object other$username = other.getUsername(); + Label_0078: { + if (this$username == null) { + if (other$username == null) { + break Label_0078; + } + } else if (this$username.equals(other$username)) { + break Label_0078; + } + return false; + } + final Object this$host = this.getHost(); + final Object other$host = other.getHost(); + if (this$host == null) { + if (other$host == null) { + return this.getPort() == other.getPort(); + } + } else if (this$host.equals(other$host)) { + return this.getPort() == other.getPort(); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof Packet2Handshake; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + this.getProcolVersion(); + final Object $username = this.getUsername(); + result = result * 31 + (($username == null) ? 0 : $username.hashCode()); + final Object $host = this.getHost(); + result = result * 31 + (($host == null) ? 0 : $host.hashCode()); + result = result * 31 + this.getPort(); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet3Chat.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet3Chat.java new file mode 100644 index 0000000..cdd0a9f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet3Chat.java @@ -0,0 +1,85 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class Packet3Chat extends DefinedPacket { + public void setMessage(String message) { + this.message = message; + } + + private String message; + + private Packet3Chat() { + super(3); + } + + public Packet3Chat(final String message) { + this(); + this.message = message; + } + + @Override + public void read(final ByteBuf buf) { + this.message = this.readString(buf); + } + + @Override + public void write(final ByteBuf buf) { + this.writeString(this.message, buf); + } + + @Override + public void handle(final AbstractPacketHandler handler) throws Exception { + handler.handle(this); + } + + public String getMessage() { + return this.message; + } + + @Override + public String toString() { + return "Packet3Chat(message=" + this.getMessage() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Packet3Chat)) { + return false; + } + final Packet3Chat other = (Packet3Chat) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$message = this.getMessage(); + final Object other$message = other.getMessage(); + if (this$message == null) { + if (other$message == null) { + return true; + } + } else if (this$message.equals(other$message)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof Packet3Chat; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $message = this.getMessage(); + result = result * 31 + (($message == null) ? 0 : $message.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet9Respawn.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet9Respawn.java new file mode 100644 index 0000000..fe5a0fa --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/Packet9Respawn.java @@ -0,0 +1,112 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class Packet9Respawn extends DefinedPacket +{ + + private int dimension; + private byte difficulty; + private byte gameMode; + private short worldHeight; + private String levelType; + + private Packet9Respawn() + { + super( 0x09 ); + } + + public Packet9Respawn(int dimension, byte difficulty, byte gameMode, short worldHeight, String levelType) + { + this(); + this.dimension = dimension; + this.difficulty = difficulty; + this.gameMode = gameMode; + this.worldHeight = worldHeight; + this.levelType = levelType; + } + + @Override + public void read(ByteBuf buf) + { + dimension = buf.readInt(); + difficulty = buf.readByte(); + gameMode = buf.readByte(); + worldHeight = buf.readShort(); + levelType = readString( buf ); + } + + @Override + public void write(ByteBuf buf) + { + buf.writeInt( dimension ); + buf.writeByte( difficulty ); + buf.writeByte( gameMode ); + buf.writeShort( worldHeight ); + writeString( levelType, buf ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + @Override + public String toString() { + return "Packet9Respawn(dimension=" + this.dimension + ", difficulty=" + this.difficulty + ", gameMode=" + this.gameMode + ", worldHeight=" + this.worldHeight + ", levelType=" + this.levelType + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Packet9Respawn)) { + return false; + } + final Packet9Respawn other = (Packet9Respawn) o; + if (!other.canEqual(this)) { + return false; + } + if (this.dimension != other.dimension) { + return false; + } + if (this.difficulty != other.difficulty) { + return false; + } + if (this.gameMode != other.gameMode) { + return false; + } + if (this.worldHeight != other.worldHeight) { + return false; + } + final Object this$levelType = this.levelType; + final Object other$levelType = other.levelType; + if (this$levelType == null) { + if (other$levelType == null) { + return true; + } + } else if (this$levelType.equals(other$levelType)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof Packet9Respawn; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + this.dimension; + result = result * 31 + this.difficulty; + result = result * 31 + this.gameMode; + result = result * 31 + this.worldHeight; + final Object $levelType = this.levelType; + result = result * 31 + (($levelType == null) ? 0 : $levelType.hashCode()); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketC9PlayerListItem.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketC9PlayerListItem.java new file mode 100644 index 0000000..b9d1152 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketC9PlayerListItem.java @@ -0,0 +1,102 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class PacketC9PlayerListItem extends DefinedPacket +{ + + private String username; + private boolean online; + private short ping; + + private PacketC9PlayerListItem() + { + super( 0xC9 ); + } + + public PacketC9PlayerListItem(String username, boolean online, short ping) + { + super( 0xC9 ); + this.username = username; + this.online = online; + this.ping = ping; + } + + @Override + public void read(ByteBuf buf) + { + username = readString( buf ); + online = buf.readBoolean(); + ping = buf.readShort(); + } + + @Override + public void write(ByteBuf buf) + { + writeString( username, buf ); + buf.writeBoolean( online ); + buf.writeShort( ping ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + public String getUsername() { + return this.username; + } + + public boolean isOnline() { + return this.online; + } + + public short getPing() { + return this.ping; + } + + @Override + public String toString() { + return "PacketC9PlayerListItem(username=" + this.getUsername() + ", online=" + this.isOnline() + ", ping=" + this.getPing() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketC9PlayerListItem)) { + return false; + } + final PacketC9PlayerListItem other = (PacketC9PlayerListItem) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$username = this.getUsername(); + final Object other$username = other.getUsername(); + if (this$username == null) { + if (other$username == null) { + return this.isOnline() == other.isOnline() && this.getPing() == other.getPing(); + } + } else if (this$username.equals(other$username)) { + return this.isOnline() == other.isOnline() && this.getPing() == other.getPing(); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PacketC9PlayerListItem; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $username = this.getUsername(); + result = result * 31 + (($username == null) ? 0 : $username.hashCode()); + result = result * 31 + (this.isOnline() ? 1231 : 1237); + result = result * 31 + this.getPing(); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCBTabComplete.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCBTabComplete.java new file mode 100644 index 0000000..defe512 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCBTabComplete.java @@ -0,0 +1,76 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; +import java.util.Arrays; +import java.util.Objects; + +public class PacketCBTabComplete extends DefinedPacket +{ + + public String getCursor() { + return cursor; + } + + public String[] getCommands() { + return commands; + } + + private String cursor; + private String[] commands; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PacketCBTabComplete that = (PacketCBTabComplete) o; + return Objects.equals(cursor, that.cursor) && Objects.deepEquals(commands, that.commands); + } + + @Override + public int hashCode() { + return Objects.hash(cursor, Arrays.hashCode(commands)); + } + + private PacketCBTabComplete() + { + super( 0xCB ); + } + + public PacketCBTabComplete(String[] alternatives) + { + this(); + commands = alternatives; + } + + @Override + public String toString() { + return "PacketCBTabComplete{" + + "cursor='" + cursor + '\'' + + ", commands=" + Arrays.toString(commands) + + '}'; + } + + @Override + public void read(ByteBuf buf) + { + cursor = readString( buf ); + } + + @Override + public void write(ByteBuf buf) + { + StringBuilder tab = new StringBuilder(); + for ( String alternative : commands ) + { + tab.append( alternative ); + tab.append( "\00" ); + } + writeString( tab.substring( 0, tab.length() - 1 ), buf ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCCSettings.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCCSettings.java new file mode 100644 index 0000000..ba63623 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCCSettings.java @@ -0,0 +1,90 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class PacketCCSettings extends DefinedPacket +{ + + private String locale; + private byte viewDistance; + private byte chatFlags; + private byte difficulty; + private boolean showCape; + + private PacketCCSettings() + { + super( 0xCC ); + } + + @Override + public void read(ByteBuf buf) + { + locale = readString( buf ); + viewDistance = buf.readByte(); + chatFlags = buf.readByte(); + difficulty = buf.readByte(); + showCape = buf.readBoolean(); + } + + @Override + public void write(ByteBuf buf) + { + writeString( locale, buf ); + buf.writeByte( viewDistance ); + buf.writeByte( chatFlags ); + buf.writeByte( difficulty ); + buf.writeBoolean( showCape ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + @Override + public String toString() { + return "PacketCCSettings(locale=" + this.locale + ", viewDistance=" + this.viewDistance + ", chatFlags=" + this.chatFlags + ", difficulty=" + this.difficulty + ", showCape=" + this.showCape + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketCCSettings)) { + return false; + } + final PacketCCSettings other = (PacketCCSettings) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$locale = this.locale; + final Object other$locale = other.locale; + if (this$locale == null) { + if (other$locale == null) { + return this.viewDistance == other.viewDistance && this.chatFlags == other.chatFlags && this.difficulty == other.difficulty && this.showCape == other.showCape; + } + } else if (this$locale.equals(other$locale)) { + return this.viewDistance == other.viewDistance && this.chatFlags == other.chatFlags && this.difficulty == other.difficulty && this.showCape == other.showCape; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PacketCCSettings; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $locale = this.locale; + result = result * 31 + (($locale == null) ? 0 : $locale.hashCode()); + result = result * 31 + this.viewDistance; + result = result * 31 + this.chatFlags; + result = result * 31 + this.difficulty; + result = result * 31 + (this.showCape ? 1231 : 1237); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCDClientStatus.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCDClientStatus.java new file mode 100644 index 0000000..9a99122 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCDClientStatus.java @@ -0,0 +1,67 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class PacketCDClientStatus extends DefinedPacket +{ + + private byte payload; + + private PacketCDClientStatus() + { + super( 0xCD ); + } + + public PacketCDClientStatus(byte payload) + { + this(); + this.payload = payload; + } + + @Override + public void read(ByteBuf buf) + { + payload = buf.readByte(); + } + + @Override + public void write(ByteBuf buf) + { + buf.writeByte( payload ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + @Override + public String toString() { + return "PacketCDClientStatus(payload=" + this.payload + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketCDClientStatus)) { + return false; + } + final PacketCDClientStatus other = (PacketCDClientStatus) o; + return other.canEqual(this) && this.payload == other.payload; + } + + public boolean canEqual(final Object other) { + return other instanceof PacketCDClientStatus; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + this.payload; + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCEScoreboardObjective.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCEScoreboardObjective.java new file mode 100644 index 0000000..24356cc --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCEScoreboardObjective.java @@ -0,0 +1,118 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class PacketCEScoreboardObjective extends DefinedPacket +{ + + private String name; + private String text; + /** + * 0 to create, 1 to remove. + */ + private byte action; + + private PacketCEScoreboardObjective() + { + super( 0xCE ); + } + + public PacketCEScoreboardObjective(String name, String text, byte action) + { + this(); + this.name = name; + this.text = text; + this.action = action; + } + + @Override + public void read(ByteBuf buf) + { + name = readString( buf ); + text = readString( buf ); + action = buf.readByte(); + } + + @Override + public void write(ByteBuf buf) + { + writeString( name, buf ); + writeString( text, buf ); + buf.writeByte( action ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + public String getName() { + return this.name; + } + + public String getText() { + return this.text; + } + + public byte getAction() { + return this.action; + } + + @Override + public String toString() { + return "PacketCEScoreboardObjective(name=" + this.getName() + ", text=" + this.getText() + ", action=" + this.getAction() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketCEScoreboardObjective)) { + return false; + } + final PacketCEScoreboardObjective other = (PacketCEScoreboardObjective) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$name = this.getName(); + final Object other$name = other.getName(); + Label_0065: { + if (this$name == null) { + if (other$name == null) { + break Label_0065; + } + } else if (this$name.equals(other$name)) { + break Label_0065; + } + return false; + } + final Object this$text = this.getText(); + final Object other$text = other.getText(); + if (this$text == null) { + if (other$text == null) { + return this.getAction() == other.getAction(); + } + } else if (this$text.equals(other$text)) { + return this.getAction() == other.getAction(); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PacketCEScoreboardObjective; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * 31 + (($name == null) ? 0 : $name.hashCode()); + final Object $text = this.getText(); + result = result * 31 + (($text == null) ? 0 : $text.hashCode()); + result = result * 31 + this.getAction(); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCFScoreboardScore.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCFScoreboardScore.java new file mode 100644 index 0000000..fb82643 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketCFScoreboardScore.java @@ -0,0 +1,127 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class PacketCFScoreboardScore extends DefinedPacket +{ + + private String itemName; + /** + * 0 = create / update, 1 = remove. + */ + private byte action; + private String scoreName; + private int value; + + private PacketCFScoreboardScore() + { + super( 0xCF ); + } + + @Override + public void read(ByteBuf buf) + { + itemName = readString( buf ); + action = buf.readByte(); + if ( action != 1 ) + { + scoreName = readString( buf ); + value = buf.readInt(); + } + } + + @Override + public void write(ByteBuf buf) + { + writeString( itemName, buf ); + buf.writeByte( action ); + if ( action != 1 ) + { + writeString( scoreName, buf ); + buf.writeInt( value ); + } + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + public String getItemName() { + return this.itemName; + } + + public byte getAction() { + return this.action; + } + + public String getScoreName() { + return this.scoreName; + } + + public int getValue() { + return this.value; + } + + @Override + public String toString() { + return "PacketCFScoreboardScore(itemName=" + this.getItemName() + ", action=" + this.getAction() + ", scoreName=" + this.getScoreName() + ", value=" + this.getValue() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketCFScoreboardScore)) { + return false; + } + final PacketCFScoreboardScore other = (PacketCFScoreboardScore) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$itemName = this.getItemName(); + final Object other$itemName = other.getItemName(); + Label_0065: { + if (this$itemName == null) { + if (other$itemName == null) { + break Label_0065; + } + } else if (this$itemName.equals(other$itemName)) { + break Label_0065; + } + return false; + } + if (this.getAction() != other.getAction()) { + return false; + } + final Object this$scoreName = this.getScoreName(); + final Object other$scoreName = other.getScoreName(); + if (this$scoreName == null) { + if (other$scoreName == null) { + return this.getValue() == other.getValue(); + } + } else if (this$scoreName.equals(other$scoreName)) { + return this.getValue() == other.getValue(); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PacketCFScoreboardScore; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $itemName = this.getItemName(); + result = result * 31 + (($itemName == null) ? 0 : $itemName.hashCode()); + result = result * 31 + this.getAction(); + final Object $scoreName = this.getScoreName(); + result = result * 31 + (($scoreName == null) ? 0 : $scoreName.hashCode()); + result = result * 31 + this.getValue(); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketD0DisplayScoreboard.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketD0DisplayScoreboard.java new file mode 100644 index 0000000..0650810 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketD0DisplayScoreboard.java @@ -0,0 +1,91 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class PacketD0DisplayScoreboard extends DefinedPacket +{ + /** + * 0 = list, 1 = side, 2 = below. + */ + private byte position; + private String name; + + private PacketD0DisplayScoreboard() + { + super( 0xD0 ); + } + + @Override + public void read(ByteBuf buf) + { + position = buf.readByte(); + name = readString( buf ); + } + + @Override + public void write(ByteBuf buf) + { + buf.writeByte( position ); + writeString( name, buf ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + public byte getPosition() { + return this.position; + } + + public String getName() { + return this.name; + } + + @Override + public String toString() { + return "PacketD0DisplayScoreboard(position=" + this.getPosition() + ", name=" + this.getName() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketD0DisplayScoreboard)) { + return false; + } + final PacketD0DisplayScoreboard other = (PacketD0DisplayScoreboard) o; + if (!other.canEqual(this)) { + return false; + } + if (this.getPosition() != other.getPosition()) { + return false; + } + final Object this$name = this.getName(); + final Object other$name = other.getName(); + if (this$name == null) { + if (other$name == null) { + return true; + } + } else if (this$name.equals(other$name)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PacketD0DisplayScoreboard; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + this.getPosition(); + final Object $name = this.getName(); + result = result * 31 + (($name == null) ? 0 : $name.hashCode()); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketD1Team.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketD1Team.java new file mode 100644 index 0000000..7088dbf --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketD1Team.java @@ -0,0 +1,212 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +import java.util.Arrays; + +public class PacketD1Team extends DefinedPacket +{ + + private String name; + /** + * 0 - create, 1 remove, 2 info update, 3 player add, 4 player remove. + */ + private byte mode; + private String displayName; + private String prefix; + private String suffix; + private boolean friendlyFire; + private short playerCount; + private String[] players; + + private PacketD1Team() + { + super( 0xD1 ); + } + + /** + * Packet to destroy a team. + * + * @param name + */ + public PacketD1Team(String name) + { + this(); + this.name = name; + this.mode = 1; + } + + @Override + public void read(ByteBuf buf) + { + name = readString( buf ); + mode = buf.readByte(); + if ( mode == 0 || mode == 2 ) + { + displayName = readString( buf ); + prefix = readString( buf ); + suffix = readString( buf ); + friendlyFire = buf.readBoolean(); + } + if ( mode == 0 || mode == 3 || mode == 4 ) + { + players = new String[ buf.readShort() ]; + for ( int i = 0; i < getPlayers().length; i++ ) + { + players[i] = readString( buf ); + } + } + } + + @Override + public void write(ByteBuf buf) + { + writeString( name, buf ); + buf.writeByte( mode ); + if ( mode == 0 || mode == 2 ) + { + writeString( displayName, buf ); + writeString( prefix, buf ); + writeString( suffix, buf ); + buf.writeBoolean( friendlyFire ); + } + if ( mode == 0 || mode == 3 || mode == 4 ) + { + buf.writeShort( players.length ); + for ( int i = 0; i < players.length; i++ ) + { + writeString( players[i], buf ); + } + } + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + public String getName() { + return this.name; + } + + public byte getMode() { + return this.mode; + } + + public String getDisplayName() { + return this.displayName; + } + + public String getPrefix() { + return this.prefix; + } + + public String getSuffix() { + return this.suffix; + } + + public boolean isFriendlyFire() { + return this.friendlyFire; + } + + public short getPlayerCount() { + return this.playerCount; + } + + public String[] getPlayers() { + return this.players; + } + + @Override + public String toString() { + return "PacketD1Team(name=" + this.getName() + ", mode=" + this.getMode() + ", displayName=" + this.getDisplayName() + ", prefix=" + this.getPrefix() + ", suffix=" + this.getSuffix() + ", friendlyFire=" + this.isFriendlyFire() + + ", playerCount=" + this.getPlayerCount() + ", players=" + Arrays.deepToString(this.getPlayers()) + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketD1Team)) { + return false; + } + final PacketD1Team other = (PacketD1Team) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$name = this.getName(); + final Object other$name = other.getName(); + Label_0065: { + if (this$name == null) { + if (other$name == null) { + break Label_0065; + } + } else if (this$name.equals(other$name)) { + break Label_0065; + } + return false; + } + if (this.getMode() != other.getMode()) { + return false; + } + final Object this$displayName = this.getDisplayName(); + final Object other$displayName = other.getDisplayName(); + Label_0115: { + if (this$displayName == null) { + if (other$displayName == null) { + break Label_0115; + } + } else if (this$displayName.equals(other$displayName)) { + break Label_0115; + } + return false; + } + final Object this$prefix = this.getPrefix(); + final Object other$prefix = other.getPrefix(); + Label_0152: { + if (this$prefix == null) { + if (other$prefix == null) { + break Label_0152; + } + } else if (this$prefix.equals(other$prefix)) { + break Label_0152; + } + return false; + } + final Object this$suffix = this.getSuffix(); + final Object other$suffix = other.getSuffix(); + if (this$suffix == null) { + if (other$suffix == null) { + return this.isFriendlyFire() == other.isFriendlyFire() && this.getPlayerCount() == other.getPlayerCount() && Arrays.deepEquals(this.getPlayers(), other.getPlayers()); + } + } else if (this$suffix.equals(other$suffix)) { + return this.isFriendlyFire() == other.isFriendlyFire() && this.getPlayerCount() == other.getPlayerCount() && Arrays.deepEquals(this.getPlayers(), other.getPlayers()); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PacketD1Team; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $name = this.getName(); + result = result * 31 + (($name == null) ? 0 : $name.hashCode()); + result = result * 31 + this.getMode(); + final Object $displayName = this.getDisplayName(); + result = result * 31 + (($displayName == null) ? 0 : $displayName.hashCode()); + final Object $prefix = this.getPrefix(); + result = result * 31 + (($prefix == null) ? 0 : $prefix.hashCode()); + final Object $suffix = this.getSuffix(); + result = result * 31 + (($suffix == null) ? 0 : $suffix.hashCode()); + result = result * 31 + (this.isFriendlyFire() ? 1231 : 1237); + result = result * 31 + this.getPlayerCount(); + result = result * 31 + Arrays.deepHashCode(this.getPlayers()); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFAPluginMessage.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFAPluginMessage.java new file mode 100644 index 0000000..fa5f9fa --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFAPluginMessage.java @@ -0,0 +1,97 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.protocol.packet; + +import java.util.Arrays; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +public class PacketFAPluginMessage extends DefinedPacket { + private String tag; + private byte[] data; + + private PacketFAPluginMessage() { + super(250); + } + + public PacketFAPluginMessage(final String tag, final byte[] data) { + this(); + this.tag = tag; + this.data = data; + } + + @Override + public void read(final ByteBuf buf) { + this.tag = this.readString(buf); + this.data = this.readArray(buf); + } + + @Override + public void write(final ByteBuf buf) { + this.writeString(this.tag, buf); + this.writeArray(this.data, buf); + } + + @Override + public void handle(final AbstractPacketHandler handler) throws Exception { + handler.handle(this); + } + + public String getTag() { + return this.tag; + } + + public byte[] getData() { + return this.data; + } + + public ByteBuf getStream(){ + return Unpooled.wrappedBuffer(this.data); + } + + @Override + public String toString() { + return "PacketFAPluginMessage(tag=" + this.getTag() + ", data=" + Arrays.toString(this.getData()) + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketFAPluginMessage)) { + return false; + } + final PacketFAPluginMessage other = (PacketFAPluginMessage) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$tag = this.getTag(); + final Object other$tag = other.getTag(); + if (this$tag == null) { + if (other$tag == null) { + return Arrays.equals(this.getData(), other.getData()); + } + } else if (this$tag.equals(other$tag)) { + return Arrays.equals(this.getData(), other.getData()); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PacketFAPluginMessage; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $tag = this.getTag(); + result = result * 31 + (($tag == null) ? 0 : $tag.hashCode()); + result = result * 31 + Arrays.hashCode(this.getData()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFCEncryptionResponse.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFCEncryptionResponse.java new file mode 100644 index 0000000..737f365 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFCEncryptionResponse.java @@ -0,0 +1,82 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +import java.util.Arrays; + +public class PacketFCEncryptionResponse extends DefinedPacket +{ + + private byte[] sharedSecret; + private byte[] verifyToken; + + private PacketFCEncryptionResponse() + { + super( 0xFC ); + } + + public PacketFCEncryptionResponse(byte[] sharedSecret, byte[] verifyToken) + { + this(); + this.sharedSecret = sharedSecret; + this.verifyToken = verifyToken; + } + + public byte[] getVerifyToken(){ + return verifyToken; + } + + public byte[] getSharedSecret(){ + return sharedSecret; + } + + @Override + public void read(ByteBuf buf) + { + sharedSecret = readArray( buf ); + verifyToken = readArray( buf ); + } + + @Override + public void write(ByteBuf buf) + { + writeArray( sharedSecret, buf ); + writeArray( verifyToken, buf ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + @Override + public String toString() { + return "PacketFCEncryptionResponse(sharedSecret=" + Arrays.toString(this.getSharedSecret()) + ", verifyToken=" + Arrays.toString(this.getVerifyToken()) + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketFCEncryptionResponse)) { + return false; + } + final PacketFCEncryptionResponse other = (PacketFCEncryptionResponse) o; + return other.canEqual(this) && Arrays.equals(this.getSharedSecret(), other.getSharedSecret()) && Arrays.equals(this.getVerifyToken(), other.getVerifyToken()); + } + + public boolean canEqual(final Object other) { + return other instanceof PacketFCEncryptionResponse; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + Arrays.hashCode(this.getSharedSecret()); + result = result * 31 + Arrays.hashCode(this.getVerifyToken()); + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFDEncryptionRequest.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFDEncryptionRequest.java new file mode 100644 index 0000000..b0f2a37 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFDEncryptionRequest.java @@ -0,0 +1,104 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +import java.util.Arrays; + +public class PacketFDEncryptionRequest extends DefinedPacket +{ + + private String serverId; + private byte[] publicKey; + private byte[] verifyToken; + + private PacketFDEncryptionRequest() + { + super( 0xFD ); + } + + public byte[] getVerifyToken(){ + return verifyToken; + } + + public byte[] getPublicKey(){ + return publicKey; + } + + public String getServerId(){ + return serverId; + } + + public PacketFDEncryptionRequest(String serverId, byte[] publicKey, byte[] verifyToken) + { + this(); + this.serverId = serverId; + this.publicKey = publicKey; + this.verifyToken = verifyToken; + } + + @Override + public void read(ByteBuf buf) + { + serverId = readString( buf ); + publicKey = readArray( buf ); + verifyToken = readArray( buf ); + } + + @Override + public void write(ByteBuf buf) + { + writeString( serverId, buf ); + writeArray( publicKey, buf ); + writeArray( verifyToken, buf ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketFDEncryptionRequest)) { + return false; + } + final PacketFDEncryptionRequest other = (PacketFDEncryptionRequest) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$serverId = this.getServerId(); + final Object other$serverId = other.getServerId(); + if (this$serverId == null) { + if (other$serverId == null) { + return Arrays.equals(this.getPublicKey(), other.getPublicKey()) && Arrays.equals(this.getVerifyToken(), other.getVerifyToken()); + } + } else if (this$serverId.equals(other$serverId)) { + return Arrays.equals(this.getPublicKey(), other.getPublicKey()) && Arrays.equals(this.getVerifyToken(), other.getVerifyToken()); + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PacketFDEncryptionRequest; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $serverId = this.getServerId(); + result = result * 31 + (($serverId == null) ? 0 : $serverId.hashCode()); + result = result * 31 + Arrays.hashCode(this.getPublicKey()); + result = result * 31 + Arrays.hashCode(this.getVerifyToken()); + return result; + } + + @Override + public String toString() { + return "PacketFDEncryptionRequest(serverId=" + this.getServerId() + ", publicKey=" + Arrays.toString(this.getPublicKey()) + ", verifyToken=" + Arrays.toString(this.getVerifyToken()) + ")"; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFEPing.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFEPing.java new file mode 100644 index 0000000..75c464f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFEPing.java @@ -0,0 +1,61 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class PacketFEPing extends DefinedPacket +{ + + private byte version; + + private PacketFEPing() + { + super( 0xFE ); + } + + @Override + public void read(ByteBuf buf) + { + version = buf.readByte(); + } + + @Override + public void write(ByteBuf buf) + { + buf.writeByte( version ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + @Override + public String toString() { + return "PacketFEPing(version=" + this.version + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketFEPing)) { + return false; + } + final PacketFEPing other = (PacketFEPing) o; + return other.canEqual(this) && this.version == other.version; + } + + public boolean canEqual(final Object other) { + return other instanceof PacketFEPing; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * 31 + this.version; + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFFKick.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFFKick.java new file mode 100644 index 0000000..15d062c --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/PacketFFKick.java @@ -0,0 +1,81 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; + +public class PacketFFKick extends DefinedPacket { + private String message; + + private PacketFFKick() { + super(255); + } + + public PacketFFKick(final String message) { + this(); + this.message = message; + } + + @Override + public void read(final ByteBuf buf) { + this.message = this.readString(buf); + } + + @Override + public void write(final ByteBuf buf) { + this.writeString(this.message, buf); + } + + @Override + public void handle(final AbstractPacketHandler handler) throws Exception { + handler.handle(this); + } + + public String getMessage() { + return this.message; + } + + @Override + public String toString() { + return "PacketFFKick(message=" + this.getMessage() + ")"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof PacketFFKick)) { + return false; + } + final PacketFFKick other = (PacketFFKick) o; + if (!other.canEqual(this)) { + return false; + } + final Object this$message = this.getMessage(); + final Object other$message = other.getMessage(); + if (this$message == null) { + if (other$message == null) { + return true; + } + } else if (this$message.equals(other$message)) { + return true; + } + return false; + } + + public boolean canEqual(final Object other) { + return other instanceof PacketFFKick; + } + + @Override + public int hashCode() { + final int PRIME = 31; + int result = 1; + final Object $message = this.getMessage(); + result = result * 31 + (($message == null) ? 0 : $message.hashCode()); + return result; + } +} \ No newline at end of file diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/forge/Forge1Login.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/forge/Forge1Login.java new file mode 100644 index 0000000..dd0d72a --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/packet/forge/Forge1Login.java @@ -0,0 +1,76 @@ +package net.md_5.bungee.protocol.packet.forge; + +import net.md_5.bungee.protocol.packet.*; +import io.netty.buffer.ByteBuf; + +public class Forge1Login extends Packet1Login +{ + + private Forge1Login() + { + super(); + } + + public Forge1Login(int entityId, String levelType, byte gameMode, int dimension, byte difficulty, byte unused, byte maxPlayers) + { + super( entityId, levelType, gameMode, dimension, difficulty, unused, maxPlayers ); + } + + @Override + public void read(ByteBuf buf) + { + entityId = buf.readInt(); + levelType = readString( buf ); + gameMode = buf.readByte(); + dimension = buf.readInt(); + difficulty = buf.readByte(); + unused = buf.readByte(); + maxPlayers = buf.readByte(); + } + + @Override + public void write(ByteBuf buf) + { + buf.writeInt( entityId ); + writeString( levelType, buf ); + buf.writeByte( gameMode ); + buf.writeInt( dimension ); + buf.writeByte( difficulty ); + buf.writeByte( unused ); + buf.writeByte( maxPlayers ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + @Override + public String toString() { + return "Forge1Login()"; + } + + @Override + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Forge1Login)) { + return false; + } + final Forge1Login other = (Forge1Login) o; + return other.canEqual(this); + } + + @Override + public boolean canEqual(final Object other) { + return other instanceof Forge1Login; + } + + @Override + public int hashCode() { + final int result = 1; + return result; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/BulkChunk.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/BulkChunk.java new file mode 100644 index 0000000..33f03e3 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/BulkChunk.java @@ -0,0 +1,16 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; + +public class BulkChunk extends Instruction +{ + + @Override + void read(ByteBuf in) + { + short count = in.readShort(); + int size = in.readInt(); + in.readBoolean(); + in.skipBytes( size + count * 12 ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/ByteHeader.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/ByteHeader.java new file mode 100644 index 0000000..d9f99e0 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/ByteHeader.java @@ -0,0 +1,24 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; + +class ByteHeader extends Instruction +{ + + private final Instruction child; + + ByteHeader(Instruction child) + { + this.child = child; + } + + @Override + void read(ByteBuf in) + { + byte size = in.readByte(); + for ( byte b = 0; b < size; b++ ) + { + child.read( in ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/Instruction.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/Instruction.java new file mode 100644 index 0000000..b85feee --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/Instruction.java @@ -0,0 +1,33 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; + +abstract class Instruction +{ + + static final Instruction BOOLEAN = new Jump( 1 ); + static final Instruction BULK_CHUNK = new BulkChunk(); + static final Instruction BYTE = new Jump( 1 ); + // BYTE_INT moved down + static final Instruction DOUBLE = new Jump( 8 ); + static final Instruction FLOAT = new Jump( 4 ); + static final Instruction INT = new Jump( 4 ); + static final Instruction INT_3 = new IntHeader( new Jump( 3 ) ); + static final Instruction INT_BYTE = new IntHeader( BYTE ); + static final Instruction ITEM = new Item(); + static final Instruction LONG = new Jump( 8 ); + static final Instruction METADATA = new MetaData(); + static final Instruction OPTIONAL_MOTION = new OptionalMotion(); + static final Instruction SHORT = new Jump( 2 ); + static final Instruction SHORT_BYTE = new ShortHeader( BYTE ); + static final Instruction SHORT_ITEM = new ShortHeader( ITEM ); + static final Instruction STRING = new ShortHeader( new Jump( 2 ) ); + static final Instruction USHORT_BYTE = new UnsignedShortByte(); + static final Instruction OPTIONAL_WINDOW = new OptionalWindow(); + // Illegal forward references below this line + static final Instruction BYTE_INT = new ByteHeader( INT ); + // Custom instructions + static final Instruction STRING_ARRAY = new ShortHeader( STRING ); + + abstract void read(ByteBuf in); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/IntHeader.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/IntHeader.java new file mode 100644 index 0000000..0b87c54 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/IntHeader.java @@ -0,0 +1,24 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; + +class IntHeader extends Instruction +{ + + private final Instruction child; + + IntHeader(Instruction child) + { + this.child = child; + } + + @Override + void read(ByteBuf in) + { + int size = in.readInt(); + for ( int i = 0; i < size; i++ ) + { + child.read( in ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/Item.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/Item.java new file mode 100644 index 0000000..cb632e3 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/Item.java @@ -0,0 +1,18 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; + +class Item extends Instruction +{ + + @Override + void read(ByteBuf in) + { + short type = in.readShort(); + if ( type >= 0 ) + { + in.skipBytes( 3 ); + SHORT_BYTE.read( in ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/Jump.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/Jump.java new file mode 100644 index 0000000..d7c451a --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/Jump.java @@ -0,0 +1,24 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; + +class Jump extends Instruction +{ + + final int len; + + Jump(int len) + { + if ( len < 0 ) + { + throw new IndexOutOfBoundsException(); + } + this.len = len; + } + + @Override + void read(ByteBuf in) + { + in.skipBytes( len ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/MetaData.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/MetaData.java new file mode 100644 index 0000000..929a664 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/MetaData.java @@ -0,0 +1,44 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; + +class MetaData extends Instruction +{ + + @Override + void read(ByteBuf in) + { + int x = in.readUnsignedByte(); + while ( x != 127 ) + { + int type = x >> 5; + switch ( type ) + { + case 0: + BYTE.read( in ); + break; + case 1: + SHORT.read( in ); + break; + case 2: + INT.read( in ); + break; + case 3: + FLOAT.read( in ); + break; + case 4: + STRING.read( in ); + break; + case 5: + ITEM.read( in ); + break; + case 6: + in.skipBytes( 12 ); // int, int, int + break; + default: + throw new IllegalArgumentException( "Unknown metadata type " + type ); + } + x = in.readUnsignedByte(); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/OptionalMotion.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/OptionalMotion.java new file mode 100644 index 0000000..45e040f --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/OptionalMotion.java @@ -0,0 +1,17 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; + +class OptionalMotion extends Instruction +{ + + @Override + void read(ByteBuf in) + { + int data = in.readInt(); + if ( data > 0 ) + { + in.skipBytes( 6 ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/OptionalWindow.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/OptionalWindow.java new file mode 100644 index 0000000..1fc7134 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/OptionalWindow.java @@ -0,0 +1,21 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; + +public class OptionalWindow extends Instruction +{ + + @Override + void read(ByteBuf in) + { + BYTE.read( in ); + byte type = in.readByte(); + STRING.read( in ); + BYTE.read( in ); + BOOLEAN.read( in ); + if ( type == 11 ) + { + INT.read( in ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/PacketReader.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/PacketReader.java new file mode 100644 index 0000000..141b718 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/PacketReader.java @@ -0,0 +1,74 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; +import java.util.ArrayList; +import java.util.List; +import net.md_5.bungee.protocol.OpCode; +import net.md_5.bungee.protocol.Protocol; + +public class PacketReader +{ + + private final Instruction[][] instructions; + + public PacketReader(Protocol protocol) + { + instructions = new Instruction[ protocol.getOpCodes().length ][]; + for ( int i = 0; i < instructions.length; i++ ) + { + List output = new ArrayList<>(); + + OpCode[] enums = protocol.getOpCodes()[i]; + if ( enums != null ) + { + for ( OpCode struct : enums ) + { + try + { + output.add( (Instruction) Instruction.class.getDeclaredField( struct.name() ).get( null ) ); + } catch ( NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex ) + { + throw new UnsupportedOperationException( "No definition for " + struct.name() ); + } + } + + List crushed = new ArrayList<>(); + int nextJumpSize = 0; + for ( Instruction child : output ) + { + if ( child instanceof Jump ) + { + nextJumpSize += ( (Jump) child ).len; + } else + { + if ( nextJumpSize != 0 ) + { + crushed.add( new Jump( nextJumpSize ) ); + } + crushed.add( child ); + nextJumpSize = 0; + } + } + if ( nextJumpSize != 0 ) + { + crushed.add( new Jump( nextJumpSize ) ); + } + + instructions[i] = crushed.toArray( new Instruction[ crushed.size() ] ); + } + } + } + + public void tryRead(short packetId, ByteBuf in) + { + Instruction[] packetDef = instructions[packetId]; + + if ( packetDef != null ) + { + for ( Instruction instruction : packetDef ) + { + instruction.read( in ); + } + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/ShortHeader.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/ShortHeader.java new file mode 100644 index 0000000..6db7189 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/ShortHeader.java @@ -0,0 +1,24 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; + +class ShortHeader extends Instruction +{ + + private final Instruction child; + + ShortHeader(Instruction child) + { + this.child = child; + } + + @Override + void read(ByteBuf in) + { + short size = in.readShort(); + for ( short s = 0; s < size; s++ ) + { + child.read( in ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/UnsignedShortByte.java b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/UnsignedShortByte.java new file mode 100644 index 0000000..3ea6ebe --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/protocol/skip/UnsignedShortByte.java @@ -0,0 +1,14 @@ +package net.md_5.bungee.protocol.skip; + +import io.netty.buffer.ByteBuf; + +class UnsignedShortByte extends Instruction +{ + + @Override + void read(ByteBuf in) + { + int size = in.readUnsignedShort(); + in.skipBytes( size ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/query/QueryHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/query/QueryHandler.java new file mode 100644 index 0000000..0a9452e --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/query/QueryHandler.java @@ -0,0 +1,142 @@ +package net.md_5.bungee.query; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.AddressedEnvelope; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.DatagramPacket; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public class QueryHandler extends SimpleChannelInboundHandler +{ + + private final ProxyServer bungee; + private final ListenerInfo listener; + + public QueryHandler(ProxyServer bungee, ListenerInfo listener){ + this.bungee = bungee; + this.listener = listener; + } + /*========================================================================*/ + private final Random random = new Random(); + private final Map sessions = new HashMap<>(); + + private void writeShort(ByteBuf buf, int s) + { + buf.order( ByteOrder.LITTLE_ENDIAN ).writeShort( s ); + } + + private void writeNumber(ByteBuf buf, int i) + { + writeString( buf, Integer.toString( i ) ); + } + + private void writeString(ByteBuf buf, String s) + { + for ( char c : s.toCharArray() ) + { + buf.writeChar( c ); + } + buf.writeByte( 0x00 ); + } + + @Override + protected void messageReceived(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception + { + ByteBuf in = msg.content(); + if ( in.readUnsignedByte() != 0xFE && in.readUnsignedByte() != 0xFD ) + { + throw new IllegalStateException( "Incorrect magic!" ); + } + + ByteBuf out = ctx.alloc().buffer(); + AddressedEnvelope response = new DatagramPacket( out, msg.sender() ); + + byte type = in.readByte(); + int sessionId = in.readInt(); + + if ( type == 0x09 ) + { + out.writeByte( 0x09 ); + out.writeInt( sessionId ); + + int challengeToken = random.nextInt(); + sessions.put( challengeToken, System.currentTimeMillis() ); + + writeNumber( out, challengeToken ); + } + + if ( type == 0x00 ) + { + int challengeToken = out.readInt(); + Long session = sessions.get( challengeToken ); + if ( session == null || System.currentTimeMillis() - session > TimeUnit.SECONDS.toMillis( 30 ) ) + { + throw new IllegalStateException( "No session!" ); + } + + out.writeByte( 0x00 ); + out.writeInt( sessionId ); + + if ( in.readableBytes() == 0 ) + { + // Short response + writeString( out, listener.getMotd() ); // MOTD + writeString( out, "SMP" ); // Game Type + writeString( out, "BungeeCord_Proxy" ); // World Name + writeNumber( out, bungee.getOnlineCount() ); // Online Count + writeNumber( out, listener.getMaxPlayers() ); // Max Players + writeShort( out, listener.getHost().getPort() ); // Port + writeString( out, listener.getHost().getHostString() ); // IP + } else if ( in.readableBytes() == 8 ) + { + // Long Response + out.writeBytes( new byte[ 11 ] ); + Map data = new HashMap<>(); + + data.put( "hostname", listener.getMotd() ); + data.put( "gametype", "SMP" ); + // Start Extra Info + data.put( "game_id", "MINECRAFT" ); + data.put( "version", bungee.getGameVersion() ); + // data.put( "plugins",""); + // End Extra Info + data.put( "map", "BungeeCord_Proxy" ); + data.put( "numplayers", Integer.toString( bungee.getOnlineCount() ) ); + data.put( "maxplayers", Integer.toString( listener.getMaxPlayers() ) ); + data.put( "hostport", Integer.toString( listener.getHost().getPort() ) ); + data.put( "hostip", listener.getHost().getHostString() ); + + for ( Map.Entry entry : data.entrySet() ) + { + writeString( out, entry.getKey() ); + writeString( out, entry.getValue() ); + + } + out.writeByte( 0x00 ); // Null + + // Padding + out.writeBytes( new byte[ 10 ] ); + // Player List + for ( ProxiedPlayer p : bungee.getPlayers() ) + { + writeString( out, p.getName() ); + } + out.writeByte( 0x00 ); // Null + } else + { + // Error! + throw new IllegalStateException( "Invalid data request packet" ); + } + } + + ctx.write(response); // this MIGHT fuck shit up + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/query/RemoteQuery.java b/eaglerbungee/src/main/java/net/md_5/bungee/query/RemoteQuery.java new file mode 100644 index 0000000..11c9c0c --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/query/RemoteQuery.java @@ -0,0 +1,31 @@ +package net.md_5.bungee.query; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.nio.NioDatagramChannel; +import java.net.InetSocketAddress; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ListenerInfo; + +public class RemoteQuery +{ + + private final ProxyServer bungee; + private final ListenerInfo listener; + + public RemoteQuery(ProxyServer bungee, ListenerInfo listener) { + this.bungee = bungee; + this.listener = listener; + } + + public void start(InetSocketAddress address, EventLoopGroup eventLoop, ChannelFutureListener future) + { + new Bootstrap() + .channel( NioDatagramChannel.class ) + .group( eventLoop ) + .handler( new QueryHandler( bungee, listener ) ) + .localAddress( address ) + .bind().addListener( future ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/reconnect/AbstractReconnectManager.java b/eaglerbungee/src/main/java/net/md_5/bungee/reconnect/AbstractReconnectManager.java new file mode 100644 index 0000000..897c2e0 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/reconnect/AbstractReconnectManager.java @@ -0,0 +1,34 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.reconnect; + +import com.google.common.base.Preconditions; + +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.ReconnectHandler; +import net.md_5.bungee.api.config.ListenerInfo; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public abstract class AbstractReconnectManager implements ReconnectHandler { + @Override + public ServerInfo getServer(final ProxiedPlayer player) { + final ListenerInfo listener = player.getPendingConnection().getListener(); + String forced = listener.getForcedHosts().get(player.getPendingConnection().getVirtualHost().getHostString()); + if (forced == null && listener.isForceDefault()) { + forced = listener.getDefaultServer(); + } + final String server = (forced == null) ? this.getStoredServer(player) : forced; + final String name = (server != null) ? server : listener.getDefaultServer(); + ServerInfo info = ProxyServer.getInstance().getServerInfo(name); + if (info == null) { + info = ProxyServer.getInstance().getServerInfo(listener.getDefaultServer()); + } + Preconditions.checkState(info != null, (Object) "Default server not defined"); + return info; + } + + protected abstract String getStoredServer(final ProxiedPlayer p0); +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/reconnect/SQLReconnectHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/reconnect/SQLReconnectHandler.java new file mode 100644 index 0000000..4e4dfbf --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/reconnect/SQLReconnectHandler.java @@ -0,0 +1,73 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.reconnect; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Level; + +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; + +public class SQLReconnectHandler extends AbstractReconnectManager { + private final Connection connection; + + public SQLReconnectHandler() throws ClassNotFoundException, SQLException { + Class.forName("org.sqlite.JDBC"); + this.connection = DriverManager.getConnection("jdbc:sqlite:bungee.sqlite"); + try (final PreparedStatement ps = this.connection.prepareStatement("CREATE TABLE IF NOT EXISTS players (playerId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,username TEXT NOT NULL UNIQUE COLLATE NOCASE,seen INTEGER,server TEXT);")) { + ps.executeUpdate(); + } + } + + @Override + protected String getStoredServer(final ProxiedPlayer player) { + String server = null; + try (final PreparedStatement ps = this.connection.prepareStatement("SELECT server FROM players WHERE username = ?")) { + ps.setString(1, player.getName()); + try (final ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + server = rs.getString(1); + } else { + try (final PreparedStatement playerUpdate = this.connection.prepareStatement("INSERT INTO players( username ) VALUES( ? )")) { + playerUpdate.setString(1, player.getName()); + playerUpdate.executeUpdate(); + } + } + } + } catch (SQLException ex) { + ProxyServer.getInstance().getLogger().log(Level.WARNING, "Could not load location for player " + player.getName(), ex); + } + return server; + } + + @Override + public void setServer(final ProxiedPlayer player) { + try (final PreparedStatement ps = this.connection.prepareStatement("UPDATE players SET server = ?, seen = ? WHERE username = ?")) { + ps.setString(1, player.getServer().getInfo().getName()); + ps.setInt(2, (int) (System.currentTimeMillis() / 1000L)); + ps.setString(3, player.getName()); + ps.executeUpdate(); + } catch (SQLException ex) { + ProxyServer.getInstance().getLogger().log(Level.WARNING, "Could not save location for player " + player.getName() + " on server " + player.getServer().getInfo().getName(), ex); + } + } + + @Override + public void save() { + } + + @Override + public void close() { + try { + this.connection.close(); + } catch (SQLException ex) { + ProxyServer.getInstance().getLogger().log(Level.WARNING, "Error closing SQLite connection", ex); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/reconnect/YamlReconnectHandler.java b/eaglerbungee/src/main/java/net/md_5/bungee/reconnect/YamlReconnectHandler.java new file mode 100644 index 0000000..6f036b7 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/reconnect/YamlReconnectHandler.java @@ -0,0 +1,114 @@ +package net.md_5.bungee.reconnect; + +import net.md_5.bungee.api.AbstractReconnectHandler; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.error.YAMLException; + +public class YamlReconnectHandler extends AbstractReconnectHandler +{ + + private final Yaml yaml = new Yaml(); + private final File file = new File( "locations.yml" ); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + /*========================================================================*/ + private Map data; + + @SuppressWarnings("unchecked") + public YamlReconnectHandler() + { + try + { + file.createNewFile(); + try ( FileReader rd = new FileReader( file ) ) + { + data = yaml.loadAs( rd, Map.class ); + } + } catch ( YAMLException ex ) + { + file.renameTo( new File( "locations.yml.old" ) ); + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Could not load reconnect locations, resetting them" ); + } catch ( IOException ex ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Could not load reconnect locations", ex ); + } + + if ( data == null ) + { + data = new HashMap<>(); + } + } + + @Override + protected ServerInfo getStoredServer(ProxiedPlayer player) + { + ServerInfo server = null; + lock.readLock().lock(); + try + { + server = ProxyServer.getInstance().getServerInfo( data.get( key( player ) ) ); + } finally + { + lock.readLock().unlock(); + } + return server; + } + + @Override + public void setServer(ProxiedPlayer player) + { + lock.writeLock().lock(); + try + { + data.put( key( player ), ( player.getReconnectServer() != null ) ? player.getReconnectServer().getName() : player.getServer().getInfo().getName() ); + } finally + { + lock.writeLock().unlock(); + } + } + + private String key(ProxiedPlayer player) + { + InetSocketAddress host = player.getPendingConnection().getVirtualHost(); + return player.getName() + ";" + host.getHostString() + ":" + host.getPort(); + } + + @Override + public void save() + { + Map copy = new HashMap<>(); + lock.readLock().lock(); + try + { + copy.putAll( data ); + } finally + { + lock.readLock().unlock(); + } + + try ( FileWriter wr = new FileWriter( file ) ) + { + yaml.dump( copy, wr ); + } catch ( IOException ex ) + { + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Could not save reconnect locations", ex ); + } + } + + @Override + public void close() + { + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/scheduler/BungeeScheduler.java b/eaglerbungee/src/main/java/net/md_5/bungee/scheduler/BungeeScheduler.java new file mode 100644 index 0000000..53048d2 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/scheduler/BungeeScheduler.java @@ -0,0 +1,85 @@ +package net.md_5.bungee.scheduler; + +import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import gnu.trove.TCollections; +import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.hash.TIntObjectHashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.scheduler.ScheduledTask; +import net.md_5.bungee.api.scheduler.TaskScheduler; + +public class BungeeScheduler implements TaskScheduler +{ + + private final ExecutorService s = Executors.newCachedThreadPool( new ThreadFactoryBuilder().setNameFormat( "Bungee Pool Thread #%1$d" ).build() ); + private final AtomicInteger taskCounter = new AtomicInteger(); + private final TIntObjectMap tasks = TCollections.synchronizedMap( new TIntObjectHashMap() ); + private final Multimap tasksByPlugin = Multimaps.synchronizedMultimap( HashMultimap.create() ); + + public void shutdown() + { + s.shutdown(); + } + + @Override + public void cancel(int id) + { + BungeeTask task = tasks.remove( id ); + task.cancel(); + tasksByPlugin.values().remove( task ); + } + + @Override + public void cancel(ScheduledTask task) + { + cancel( task.getId() ); + } + + @Override + public int cancel(Plugin plugin) + { + Set toRemove = new HashSet<>(); + for ( ScheduledTask task : tasksByPlugin.get( plugin ) ) + { + toRemove.add( task ); + } + for ( ScheduledTask task : toRemove ) + { + cancel( task ); + } + return toRemove.size(); + } + + @Override + public ScheduledTask runAsync(Plugin owner, Runnable task) + { + return schedule( owner, task, 0, TimeUnit.MILLISECONDS ); + } + + @Override + public ScheduledTask schedule(Plugin owner, Runnable task, long delay, TimeUnit unit) + { + return schedule( owner, task, delay, 0, unit ); + } + + @Override + public ScheduledTask schedule(Plugin owner, Runnable task, long delay, long period, TimeUnit unit) + { + Preconditions.checkNotNull( owner, "owner" ); + Preconditions.checkNotNull( task, "task" ); + BungeeTask prepared = new BungeeTask( this, taskCounter.getAndIncrement(), owner, task, delay, period, unit ); + tasks.put( prepared.getId(), prepared ); + s.execute( prepared ); + return prepared; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/scheduler/BungeeTask.java b/eaglerbungee/src/main/java/net/md_5/bungee/scheduler/BungeeTask.java new file mode 100644 index 0000000..4f6170b --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/scheduler/BungeeTask.java @@ -0,0 +1,94 @@ +package net.md_5.bungee.scheduler; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.scheduler.ScheduledTask; + +public class BungeeTask implements Runnable, ScheduledTask +{ + + private final BungeeScheduler sched; + private final int id; + private final Plugin owner; + private final Runnable task; + // + private final long delay; + private final long period; + private final AtomicBoolean running = new AtomicBoolean( true ); + + public BungeeTask(BungeeScheduler sched, int id, Plugin owner, Runnable task, long delay, long period, TimeUnit unit) + { + this.sched = sched; + this.id = id; + this.owner = owner; + this.task = task; + this.delay = unit.toMillis( delay ); + this.period = unit.toMillis( period ); + } + + @Override + public int getId() { + return this.id; + } + + @Override + public Plugin getOwner() { + return this.owner; + } + + @Override + public Runnable getTask() { + return this.task; + } + + @Override + public void cancel() + { + running.set( false ); + } + + @Override + public void run() + { + if ( delay > 0 ) + { + try + { + Thread.sleep( delay ); + } catch ( InterruptedException ex ) + { + Thread.currentThread().interrupt(); + } + } + + while ( running.get() ) + { + try + { + task.run(); + } catch ( Throwable t ) + { + ProxyServer.getInstance().getLogger().log( Level.SEVERE, String.format( "Task %s encountered an exception", this ), t ); + } + + // If we have a period of 0 or less, only run once + if ( period <= 0 ) + { + break; + } + + try + { + Thread.sleep( period ); + } catch ( InterruptedException ex ) + { + Thread.currentThread().interrupt(); + } + } + + sched.cancel( this ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/scheduler/BungeeThreadPool.java b/eaglerbungee/src/main/java/net/md_5/bungee/scheduler/BungeeThreadPool.java new file mode 100644 index 0000000..e17f277 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/scheduler/BungeeThreadPool.java @@ -0,0 +1,28 @@ +// +// Decompiled by Procyon v0.5.36 +// + +package net.md_5.bungee.scheduler; + +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +import net.md_5.bungee.api.ProxyServer; + +public class BungeeThreadPool extends ScheduledThreadPoolExecutor { + public BungeeThreadPool(final ThreadFactory threadFactory) { + super(Integer.MAX_VALUE, threadFactory); + this.setKeepAliveTime(5L, TimeUnit.MINUTES); + this.allowCoreThreadTimeOut(true); + } + + @Override + protected void afterExecute(final Runnable r, final Throwable t) { + super.afterExecute(r, t); + if (t != null) { + ProxyServer.getInstance().getLogger().log(Level.SEVERE, "Task caused exception whilst running", t); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/tab/Custom.java b/eaglerbungee/src/main/java/net/md_5/bungee/tab/Custom.java new file mode 100644 index 0000000..b38ae4b --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/tab/Custom.java @@ -0,0 +1,161 @@ +package net.md_5.bungee.tab; + +import com.google.common.base.Preconditions; +import java.util.Collection; +import java.util.HashSet; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.tab.CustomTabList; +import net.md_5.bungee.api.tab.TabListAdapter; +import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem; + +public class Custom extends TabListAdapter implements CustomTabList +{ + + private static final int ROWS = 20; + private static final int COLUMNS = 3; + private static final char[] FILLER = new char[] + { + '0', '1', '2', '2', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + private static final int MAX_LEN = 16; + /*========================================================================*/ + private final Collection sentStuff = new HashSet<>(); + /*========================================================================*/ + private String[][] sent = new String[ ROWS ][ COLUMNS ]; + private String[][] slots = new String[ ROWS ][ COLUMNS ]; + private int rowLim; + private int colLim; + + public Custom(ProxiedPlayer player) + { + this.init( player ); + } + + @Override + public synchronized String setSlot(int row, int column, String text) + { + return setSlot( row, column, text, true ); + } + + @Override + public synchronized String setSlot(int row, int column, String text, boolean update) + { + Preconditions.checkArgument( row > 0 && row <= ROWS, "row out of range" ); + Preconditions.checkArgument( column > 0 && column <= COLUMNS, "column out of range" ); + + if ( text != null ) + { + Preconditions.checkArgument( text.length() <= MAX_LEN - 2, "text must be <= %s chars", MAX_LEN - 2 ); + Preconditions.checkArgument( !ChatColor.stripColor( text ).isEmpty(), "Text cannot consist entirely of colour codes" ); + text = attempt( text ); + sentStuff.add( text ); + + if ( rowLim < row || colLim < column ) + { + rowLim = row; + colLim = column; + } + } else + { + sentStuff.remove( text ); + } + + slots[--row][--column] = text; + if ( update ) + { + update(); + } + return text; + } + + private String attempt(String s) + { + for ( char c : FILLER ) + { + String attempt = s + Character.toString( ChatColor.COLOR_CHAR ) + c; + if ( !sentStuff.contains( attempt ) ) + { + return attempt; + } + } + if ( s.length() <= MAX_LEN - 4 ) + { + return attempt( s + Character.toString( ChatColor.COLOR_CHAR ) + FILLER[0] ); + } + throw new IllegalArgumentException( "List already contains all variants of string" ); + } + + @Override + public synchronized void update() + { + clear(); + for ( int i = 0; i < rowLim; i++ ) + { + for ( int j = 0; j < colLim; j++ ) + { + String text = ( slots[i][j] != null ) ? slots[i][j] : new StringBuilder().append( base( i ) ).append( base( j ) ).toString(); + sent[i][j] = text; + getPlayer().unsafe().sendPacket( new PacketC9PlayerListItem( text, true, (short) 0 ) ); + } + } + } + + @Override + public synchronized void clear() + { + for ( int i = 0; i < rowLim; i++ ) + { + for ( int j = 0; j < colLim; j++ ) + { + if ( sent[i][j] != null ) + { + String text = sent[i][j]; + sent[i][j] = null; + getPlayer().unsafe().sendPacket( new PacketC9PlayerListItem( text, false, (short) 9999 ) ); + } + } + } + } + + @Override + public synchronized int getRows() + { + return ROWS; + } + + @Override + public synchronized int getColumns() + { + return COLUMNS; + } + + @Override + public synchronized int getSize() + { + return ROWS * COLUMNS; + } + + @Override + public boolean onListUpdate(String name, boolean online, int ping) + { + return false; + } + + private static char[] base(int n) + { + String hex = Integer.toHexString( n + 1 ); + char[] alloc = new char[ hex.length() * 2 ]; + for ( int i = 0; i < alloc.length; i++ ) + { + if ( i % 2 == 0 ) + { + alloc[i] = ChatColor.COLOR_CHAR; + } else + { + alloc[i] = hex.charAt( i / 2 ); + } + } + return alloc; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/tab/Global.java b/eaglerbungee/src/main/java/net/md_5/bungee/tab/Global.java new file mode 100644 index 0000000..ccc4f01 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/tab/Global.java @@ -0,0 +1,45 @@ +package net.md_5.bungee.tab; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.tab.TabListAdapter; +import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem; + +public class Global extends TabListAdapter +{ + + private boolean sentPing; + + @Override + public void onConnect() + { + for ( ProxiedPlayer p : ProxyServer.getInstance().getPlayers() ) + { + getPlayer().unsafe().sendPacket( new PacketC9PlayerListItem( p.getDisplayName(), true, (short) p.getPing() ) ); + } + BungeeCord.getInstance().broadcast( new PacketC9PlayerListItem( getPlayer().getDisplayName(), true, (short) getPlayer().getPing() ) ); + } + + @Override + public void onPingChange(int ping) + { + if ( !sentPing ) + { + sentPing = true; + BungeeCord.getInstance().broadcast( new PacketC9PlayerListItem( getPlayer().getDisplayName(), true, (short) getPlayer().getPing() ) ); + } + } + + @Override + public void onDisconnect() + { + BungeeCord.getInstance().broadcast( new PacketC9PlayerListItem( getPlayer().getDisplayName(), false, (short) 9999 ) ); + } + + @Override + public boolean onListUpdate(String name, boolean online, int ping) + { + return false; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/tab/GlobalPing.java b/eaglerbungee/src/main/java/net/md_5/bungee/tab/GlobalPing.java new file mode 100644 index 0000000..c7233a2 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/tab/GlobalPing.java @@ -0,0 +1,23 @@ +package net.md_5.bungee.tab; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem; + +public class GlobalPing extends Global +{ + + private static final int PING_THRESHOLD = 20; + /*========================================================================*/ + private int lastPing; + + @Override + public void onPingChange(int ping) + { + if ( ping - PING_THRESHOLD > lastPing && ping + PING_THRESHOLD < lastPing ) + { + lastPing = ping; + BungeeCord.getInstance().broadcast( new PacketC9PlayerListItem( getPlayer().getDisplayName(), true, (short) ping ) ); + } + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/tab/ServerUnique.java b/eaglerbungee/src/main/java/net/md_5/bungee/tab/ServerUnique.java new file mode 100644 index 0000000..2e5dad3 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/tab/ServerUnique.java @@ -0,0 +1,39 @@ +package net.md_5.bungee.tab; + +import java.util.Collection; +import java.util.HashSet; +import net.md_5.bungee.api.tab.TabListAdapter; +import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem; + +public class ServerUnique extends TabListAdapter +{ + + private final Collection usernames = new HashSet<>(); + + @Override + public void onServerChange() + { + synchronized ( usernames ) + { + for ( String username : usernames ) + { + this.getPlayer().unsafe().sendPacket( new PacketC9PlayerListItem( username, false, (short) 9999 ) ); + } + usernames.clear(); + } + } + + @Override + public boolean onListUpdate(String name, boolean online, int ping) + { + if ( online ) + { + usernames.add( name ); + } else + { + usernames.remove( name ); + } + + return true; + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/util/CaseInsensitiveHashingStrategy.java b/eaglerbungee/src/main/java/net/md_5/bungee/util/CaseInsensitiveHashingStrategy.java new file mode 100644 index 0000000..e757f7e --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/util/CaseInsensitiveHashingStrategy.java @@ -0,0 +1,21 @@ +package net.md_5.bungee.util; + +import gnu.trove.strategy.HashingStrategy; + +class CaseInsensitiveHashingStrategy implements HashingStrategy +{ + + static final CaseInsensitiveHashingStrategy INSTANCE = new CaseInsensitiveHashingStrategy(); + + @Override + public int computeHashCode(Object object) + { + return ( (String) object ).toLowerCase().hashCode(); + } + + @Override + public boolean equals(Object o1, Object o2) + { + return o1.equals( o2 ) || ( o1 instanceof String && o2 instanceof String && ( (String) o1 ).toLowerCase().equals( ( (String) o2 ).toLowerCase() ) ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/util/CaseInsensitiveMap.java b/eaglerbungee/src/main/java/net/md_5/bungee/util/CaseInsensitiveMap.java new file mode 100644 index 0000000..ddfa7f5 --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/util/CaseInsensitiveMap.java @@ -0,0 +1,18 @@ +package net.md_5.bungee.util; + +import gnu.trove.map.hash.TCustomHashMap; +import java.util.Map; + +public class CaseInsensitiveMap extends TCustomHashMap +{ + + public CaseInsensitiveMap() + { + super( CaseInsensitiveHashingStrategy.INSTANCE ); + } + + public CaseInsensitiveMap(Map map) + { + super( CaseInsensitiveHashingStrategy.INSTANCE, map ); + } +} diff --git a/eaglerbungee/src/main/java/net/md_5/bungee/util/CaseInsensitiveSet.java b/eaglerbungee/src/main/java/net/md_5/bungee/util/CaseInsensitiveSet.java new file mode 100644 index 0000000..579c6fd --- /dev/null +++ b/eaglerbungee/src/main/java/net/md_5/bungee/util/CaseInsensitiveSet.java @@ -0,0 +1,18 @@ +package net.md_5.bungee.util; + +import gnu.trove.set.hash.TCustomHashSet; +import java.util.Collection; + +public class CaseInsensitiveSet extends TCustomHashSet +{ + + public CaseInsensitiveSet() + { + super( CaseInsensitiveHashingStrategy.INSTANCE ); + } + + public CaseInsensitiveSet(Collection collection) + { + super( CaseInsensitiveHashingStrategy.INSTANCE, collection ); + } +} diff --git a/eaglerbungee/src/main/resources/messages.properties b/eaglerbungee/src/main/resources/messages.properties new file mode 100644 index 0000000..ca7e615 --- /dev/null +++ b/eaglerbungee/src/main/resources/messages.properties @@ -0,0 +1,21 @@ +alert=\u00a78[\u00a74Alert\u00a78]\u00a7r +already_connected=\u00a7cYou are already connected to this server +already_connecting=\u00a7cAlready connecting to this server! +command_list=\u00a7a[{0}] \u00a7e({1}): \u00a7r{2} +connect_kick=\u00a7cKicked whilst connecting to +current_server=\u00a76You are currently connected to +fallback_kick=\u00a7cCould not connect to default server, please try again later: +fallback_lobby=\u00a7cCould not connect to target server, you have been moved to the lobby server +lost_connection=[Proxy] Lost connection to server. +mojang_fail=Error occurred while contacting login servers, are they down? +no_permission=\u00a7cYou do not have permission to execute this command! +no_server=\u00a7cThe specified server does not exist +no_server_permission=\u00a7cYou don't have permission to access this server +outdated_client=Outdated Client! +outdated_server=Outdated Server! +proxy_full=Server is full +restart=[Proxy] Proxy restarting. +server_kick=[Kicked] +server_list=\u00a76You may connect to the following servers at this time: +server_went_down=\u00a7cThe server you were previously on went down, you have been connected to the lobby +total_players=Total players online: {0} diff --git a/lwjgl-rundir/_eagstorage.g.dat b/lwjgl-rundir/_eagstorage.g.dat index c180ade..dd9ac91 100644 Binary files a/lwjgl-rundir/_eagstorage.g.dat and b/lwjgl-rundir/_eagstorage.g.dat differ