160 Commits

Author SHA1 Message Date
md_5
edc5b4dc91 Close #656 - fix find filter. 2013-10-11 10:24:55 +11:00
md_5
220a95aece We cannot throttle like this if we want 1.7 compat, lets disable it until we work on a more compatible one. 2013-10-10 07:25:20 +11:00
md_5
4685099808 Close #564 adding a blank arg is a silly idea 2013-10-09 22:37:27 +11:00
md-5
6c14f40108 Reduce the time we wait for legacy pings. 2013-10-08 09:06:28 +11:00
md_5
b041d84063 Null check plugin input for servers 2013-10-07 16:55:29 +11:00
md_5
a9d3d9461f Mojang nerfed tab lists :( 2013-10-06 08:42:52 +11:00
md_5
3fc7064997 Add missing setters for handshake. 2013-10-05 09:50:20 +10:00
md_5
80001aa1f0 UDP + Java + IPV6 != Friends 2013-09-30 17:58:02 +10:00
marvin
a0d94282f6 Add PlayerHandshakeEvent which allows changing of versions and online mode status amongst other things. 2013-09-30 09:22:49 +10:00
md_5
33e11f4c44 Allow for iterables to be CSV-ified 2013-09-29 18:49:20 +10:00
md_5
b541e7aa76 Custom glist formatting 2013-09-29 18:45:11 +10:00
zaiyers
dd06937a3b changes to tab completion
* PacketCBTabComplete: options for completion should be seperated by
NUL
 * PluginManager: append an empty argument to arguments if command ends
with a whitespace (this will match all suggestions)
 * PlayerCommand: suggest only matching players instead of all players
2013-09-29 09:37:39 +10:00
md_5
891dc87b16 Allow unrecognised command line args 2013-09-28 21:00:38 +10:00
md_5
8e77cb35ff Use JDK map for throttle 2013-09-28 18:32:39 +10:00
md_5
59b32a8621 Remove access from PendingConnection interface 2013-09-28 17:37:30 +10:00
md_5
02324206e3 Clean up packets a tad 2013-09-28 17:03:33 +10:00
md_5
73ce828e6e [#637] - Fix resource bundle resolution 2013-09-26 09:24:30 +10:00
md_5
79d04bec2e [#582] Widen synchronized block for packet queue. 2013-09-26 09:20:52 +10:00
md_5
cbcd874d47 Close #626 - command line argument for version. Also refactors into a bootstrap which warns users when not using Java 7! 2013-09-25 17:21:03 +10:00
md_5
09f123ce9a guery -> query 2013-09-24 17:50:44 +10:00
md_5
0a5f8556fe Add config saving 2013-09-24 12:14:47 +10:00
md_5
103a509f26 Fix access of yaml config 2013-09-24 12:11:04 +10:00
md_5
32a5271dc3 Implement basic udp query to close #185 2013-09-24 10:09:55 +10:00
md_5
14389eb370 Use correct main class 2013-09-23 10:30:41 +10:00
md_5
a8b6a6b4aa Finish basic Yaml configuration API, complete with unit tests. Needs a lot of work with regards to how sections are handled, open to massive improvements from anyone that has more know-how. 2013-09-23 10:28:30 +10:00
md_5
8133304cce Use = for properties 2013-09-22 17:37:06 +10:00
md_5
3e8c21a485 Add specific exception for bad packets. 2013-09-21 16:57:17 +10:00
Matty Southall
f12dcc72d9 Fix compile error when compiling on OS X 2013-09-21 10:55:39 +10:00
md_5
a7a32509c7 find * -type f -print0 | xargs -0 sed -i 's/1.6.2/1.6.4/g' 2013-09-20 19:51:57 +10:00
md_5
703a393888 Procol -> Protocol. Fix typo, thanks @libraryaddict 2013-09-20 15:41:30 +10:00
md_5
3c961cd5d9 1.6.4 - MOJANG 2013-09-20 08:23:06 +10:00
md_5
12ee68a315 Update to 1.6.3 2013-09-19 17:41:01 +10:00
md_5
db5510cc4e Only interrupt tab completion if we have things to complete! 2013-09-17 10:28:51 +10:00
md_5
5ed5c71aea Move AbstractReconnectManager to the API and rename to AbstractReconnectHandler. 2013-09-16 08:21:53 +10:00
md_5
38a8469ab4 Cap command completion to one argument 2013-09-15 15:14:47 +10:00
md_5
9538dcf4d4 Properly tab complete 2013-09-15 14:04:51 +10:00
md_5
33f654ce6f *unused imports 2013-09-15 07:44:13 +10:00
md_5
c108e4e1ce Server command completion 2013-09-15 07:43:50 +10:00
md_5
e998faeec1 Add tab completion for find command. Also change api a bit. 2013-09-15 07:37:20 +10:00
md_5
d67acd7bc9 Add functionality to replicate #336 2013-09-15 07:29:22 +10:00
md_5
702f434db1 Add API to support #468 - force setting of reconnect server 2013-09-15 07:12:58 +10:00
md_5
47b5631562 Not part of the contract 2013-09-15 06:54:58 +10:00
Robin Lambertz
80e23d6646 Allow removal of listeners / commands by plugin 2013-09-15 06:52:46 +10:00
md_5
1dca12cffb Use boolean not binary and 2013-09-15 06:48:06 +10:00
md_5
29c897c9cf Add Tab Completion loosely based on @TheUnnamedDude's work. 2013-09-15 06:46:10 +10:00
md_5
042f47cbb9 Wrapped buffers are not thread safe. 2013-09-10 21:33:44 +10:00
md_5
422e97f495 Don't let pingbuf be released 2013-09-10 21:26:59 +10:00
md_5
08789d8f9f Write down a supported message type (ByteBuf) when using the ping handler. 2013-09-10 20:56:40 +10:00
md_5
96444ae304 Fix a message consisting only of a space causing the player to be kicked. 2013-09-10 16:23:05 +10:00
md_5
af58db8a67 Simpler, unit tested throttle to close #613 2013-09-10 12:02:29 +10:00
md_5
49cffebd9b Dynamic build dates - see #526 2013-09-10 11:37:48 +10:00
md_5
ffdb917f2c Use translation - closes #578 2013-09-09 14:58:56 +10:00
md_5
9c9657e36d Update links 2013-09-09 14:41:58 +10:00
md_5
1342baed47 Use fluid bytes instead of enum + format. 2013-09-09 14:36:48 +10:00
Dabo Ross
e3a7490bcd Fix formatting error - not sure how that happened 2013-09-09 14:27:23 +10:00
Dabo Ross
3e8693793c Add multiple listeners to EventPriorityTest 2013-09-09 14:27:23 +10:00
Dabo Ross
bb47aba682 Add UnregisteringListenerTest 2013-09-09 14:27:23 +10:00
Dabo Ross
d0e5ee4e09 Don't try to bake handlers when there are no more handlers. Remove them instead! 2013-09-09 14:27:23 +10:00
Dabo Ross
07e330b005 Create test for event priorities 2013-09-09 14:27:23 +10:00
Dabo Ross
024288e587 Added a Map<Class<?>, EventHandlerMethod[]> and implemented using it. I believe this should have a positive performance effect. 2013-09-09 14:27:23 +10:00
Dabo Ross
53a6bb1dee Added EventHandlerMethod wrapper for Listener and Method 2013-09-09 14:27:23 +10:00
Dabo Ross
0f06b2c4e0 Implement usage of EventPriority in EventBus 2013-09-09 14:27:23 +10:00
Dabo Ross
d3c1acce83 Add EventPriority method to EventHandler 2013-09-09 14:27:23 +10:00
Dabo Ross
eaea090d37 Add EventPriority 2013-09-09 14:27:22 +10:00
md_5
7384e797fc Bump date 2013-09-08 11:49:27 +10:00
md_5
d4d93ddbb9 Bump to Netty 4.0.9 2013-09-08 08:45:37 +10:00
md_5
ccdf2a89d8 Close #518 - use csv method for perms command 2013-09-07 12:22:43 +10:00
md_5
89edb00c05 Properly cancel tasks! 2013-09-05 19:52:41 +10:00
md_5
00a0277a13 Just call our own logger, screw jdk logger parenting 2013-09-03 11:36:00 +10:00
md_5
68f11e46f7 Depend updates. 2013-08-27 12:30:00 +10:00
Ammar Askar
c352e854ee Catch exceptions when disabling plugins 2013-08-25 10:27:25 +10:00
md_5
d8c92cd311 Add ConnectOther channel for moving other players from a plugin 2013-08-25 10:23:17 +10:00
md_5
99f361ca77 Instead of storing packets about to be passed on as a byte array, store them as a Netty buffer, which is likely to be pooled, direct and manually memory managed leading to increased performance and less GC strain. In order to ensure no resources are leaked, we free them at the end of each handle cycle if they have not been passed to a channel for writing. In initial profiles this now causes encryption to be one of the most intensive parts of BungeeCord, however in depth profiling snapshots may provide further routes for optimization. 2013-08-20 19:29:43 +10:00
md_5
738ed99d54 Code format. 2013-08-20 19:28:09 +10:00
md_5
ad0da59267 Really need to automatically do this. Add a few weeks to expire time 2013-08-20 18:50:52 +10:00
md_5
1dcc8d6a4b Close #572 - kick event message 2013-08-20 11:18:51 +10:00
md_5
0840a77153 Dem dates :( 2013-08-15 07:44:33 +10:00
md_5
61a93a54a9 ammar2 missed a spot 2013-08-14 22:14:32 +10:00
md_5
da0281508e Oi! Get back inside of that if statement. NOW! 2013-08-13 18:53:16 +10:00
Ammar Askar
51e92de2dd Only save to reconnectHandler if we have a listener that isn't forcing to the default server 2013-08-13 18:50:19 +10:00
md_5
f948acd634 Don't loop registering of listeners 2013-08-12 20:31:51 +10:00
md_5
773ce089c1 Fix http client 2013-08-10 07:30:41 +10:00
md_5
a07eba7965 Netty 4.0.7.Final - fixes memory leak 2013-08-09 19:50:48 +10:00
md_5
b68b6a76c7 Recover from broken yaml 2013-08-09 17:23:16 +10:00
md_5
332033bb02 Disable resource leak detector for ~15% cpu reduction 2013-08-09 16:58:14 +10:00
md_5
172b8bc75b Update to Netty 4.0.6-Final 2013-08-09 16:56:09 +10:00
md_5
db5a147491 Revert changes to SeverConnectedEvent 2013-08-06 11:14:54 +10:00
md_5
f083e27649 More translations! 2013-08-05 17:29:47 +10:00
md_5
b64a7be19b Bump date to the 9th 2013-08-04 21:34:56 +10:00
md_5
c4d60a8fa9 Hold player for ServerSwitchEvent - see #539 2013-08-04 21:28:10 +10:00
md_5
f07cfe0cf7 Make the ServerConnectedEvent async to allow stalling it. Closes #538 by @BjoernAkAManf. 2013-08-04 20:58:17 +10:00
md_5
4463b0c1b2 Use Java 7 API to make classloader thread safe. Closes #516 2013-08-04 20:23:31 +10:00
mrapple
ee8f33c196 Add State to ServerKickEvent 2013-08-04 18:57:13 +10:00
md_5
14ac2dd308 Allow setting whether to bind to the local address. 2013-08-04 18:56:48 +10:00
md_5
fb94612315 Fix throttle to 1) Work, 2) Not throttle outbound connections 2013-08-02 19:31:46 +10:00
md_5
4c96880580 Lets just silence java.util.NoSuchElementException: decrypt - PEOPLE DON'T UNDERSTAND ITS NOT AN ERROR. 2013-08-02 19:11:16 +10:00
md_5
4c4cdd51a1 Downgrade to Netty CR9 2013-08-02 07:32:42 +10:00
md_5
1f38152530 [URGENT] Add connection throttle. 2013-08-01 13:37:32 +10:00
md_5
911f08d52c Disable packet grouping in an attempt to increase reliability. 2013-07-31 20:18:54 +10:00
md_5
8f961c9d4e Put colours in default motd to try and force quoting in the dumped yaml 2013-07-27 12:12:12 +10:00
md_5
8a5d8a57f7 Don't infinite loop on tasks with no period 2013-07-24 17:38:46 +10:00
md_5
c54553d0f9 How kind of @lazertester to test the new scheduler! 2013-07-24 17:32:08 +10:00
md_5
600a1b4ff5 Update expire date 2013-07-24 17:02:37 +10:00
md_5
09e592295f Update to Netty 4.0.4-Final 2013-07-24 15:58:02 +10:00
md_5
fa0ee02beb Update Netty - if you guys could verify the leak has been fixed, that would be great, #523 2013-07-22 19:40:14 +10:00
md_5
b23b54d9e4 Bump Netty minor version 2013-07-19 19:31:46 +10:00
md_5
b3e8feb4cb Update POMs to 1.6.2 2013-07-18 20:13:07 +10:00
md_5
d0d1562155 Hi, I'm Mojang and I make hundreds of millions of dollars a year. I still like to think I am an Indy company, so I randomly remove existing game features and don't provide replacements.
Removes Texture Pack setting
2013-07-18 20:10:45 +10:00
md_5
f510ab2a0b Update to netty 4 final, exciting! 2013-07-17 16:47:49 +10:00
md_5
fb1cab499d Always use eventloop since we cannot be sure about plugins, thanks @MonsieurApple for the report. 2013-07-12 09:29:53 +10:00
md_5
58ca63e2b1 Use a defensive copy for shorter lock time 2013-07-11 11:14:12 +10:00
md_5
499337c98e Optimized yaml reconnect locations! Ding dong sqlite is dead.. 2013-07-11 11:12:43 +10:00
md_5
526137be7b Remove our packet queuing as it may be contributing to high CPU usage and/or memory leaks. 2013-07-11 10:14:51 +10:00
md_5
47839cb11c writeStringUTF8WithoutLengthHeaderBecause @Dinnerbone StuffedUpTheMCBrandPacket 2013-07-10 23:42:50 +10:00
md_5
55a6cc56ef Recycle messagelist when channel goes inactive (reverted from commit daa58ffe58) 2013-07-10 23:41:37 +10:00
md_5
8c2bea5be2 Fix possible race condition with regards to abandoning servers 2013-07-10 13:02:25 +10:00
md_5
daa58ffe58 Recycle messagelist when channel goes inactive 2013-07-10 09:18:58 +10:00
md_5
0189ad9c17 Add disabled commands 2013-07-09 14:55:27 +10:00
md_5
9adcb05d45 Fix 1.6.2 support 2013-07-08 22:37:59 +10:00
md_5
10e81041b2 Update to 1.6.2 2013-07-08 21:29:09 +10:00
md_5
0c56945ffd Implement upcoming Minecraft API to get the server brand - thanks @Grum for the hint. 2013-07-08 21:29:09 +10:00
md_5
0a36cbd5bc Fix compilation -> add silly catch block 2013-07-08 08:41:11 +10:00
md_5
61b4777177 Use more threads for IO to eliminate resource starvation possibly leading to high CPU usage. This brings the count back in line with what we had pre 1.6 / late 1.5 2013-07-08 08:35:12 +10:00
md_5
7d1904584b Back to Java cipher, they are both the same speed 2013-07-08 08:33:25 +10:00
md_5
475571986c Make sure packets are written before closing 2013-07-07 13:08:25 +10:00
md_5
55c2bcd634 Undo recent SQLite changes - might need to seek *another* DB engine - whats H2 like for concurrency? 2013-07-06 08:30:38 +10:00
md_5
db4abfe486 Expand streams a little bit 2013-07-05 19:17:25 +10:00
md_5
9424bdedca Implement basic MC stream 2013-07-05 19:06:46 +10:00
md_5
52b3c6b77c Dont need to force use of eventloop anymore 2013-07-05 09:32:20 +10:00
md_5
be29799f5a [Beta] Implement own HTTP client for online mode checks, instead of asynchttpclient 2013-07-05 09:29:28 +10:00
md_5
c0d581d41f Rework SQLite again to use thread local connections - closes #492 2013-07-05 08:23:29 +10:00
md_5
6b50c7c599 Move HTTP client stuffs into bungee-proxy 2013-07-04 21:59:38 +10:00
hyperring
b4101874cc Fixed forced_hosts MOTD
Noticed a tiny bug in creating a ServerPing response. The response was still using the old listener.getMotd() when it should be using the new motd variable (to take advantage of the new forced_hosts MOTD methods).
2013-07-04 21:39:33 +10:00
md_5
66de4c95ef Implement BouncyCastle as the cipher engine. 2013-07-04 21:13:10 +10:00
md_5
927a295add Add SSL support 2013-07-04 11:48:09 +10:00
md_5
2cbea83c02 HTTP is working, still need to do HTTPS though 2013-07-04 11:32:36 +10:00
md_5
87884ad084 Downgrade maven compiler - new one is still derp 2013-07-04 11:12:25 +10:00
md_5
94cc2412e7 Flush pending messages when manipulating pipeline - fixes forge support 2013-07-04 11:11:57 +10:00
md_5
924b90e325 Add statr of work on HTTP client. 2013-07-04 10:52:54 +10:00
md_5
3f476a30b4 Get depend name right :p 2013-07-04 10:31:00 +10:00
md_5
f579b31bca Add framework for HTTP api 2013-07-04 10:29:37 +10:00
md_5
cac35116c3 Import cleanup 2013-07-04 10:03:41 +10:00
md_5
bd42fb23a0 Update to Netty CR9 2013-07-04 09:58:29 +10:00
md_5
ffbebaff69 Remove old @Subscribe event handling 2013-07-04 09:43:32 +10:00
md_5
b741722e5d Close #489 - disable resource leak detector for performance reasons 2013-07-04 09:22:27 +10:00
md_5
07288c722c Update maven compiler version 2013-07-02 21:04:10 +10:00
md_5
85e82a2e34 Update POMs to 1.6.1 2013-07-02 20:59:04 +10:00
md_5
3aef35ccbb Warn about non existant fallback 2013-07-02 19:43:48 +10:00
md_5
d1760dad93 Custom outdated messages, tick! 2013-07-02 19:26:21 +10:00
md_5
d3d11cf283 Update to netty CR8 2013-07-02 15:31:20 +10:00
md_5
d3bada58d4 Close #469 - chat event setMessage 2013-07-02 10:45:37 +10:00
md-5
23517a9a97 Merge pull request #476 from vemacs/patch-1
Supposedly fix walk speeds
2013-07-01 14:10:43 -07:00
vemacs
fdc87e88f5 Supposedly fix walk speeds 2013-07-01 15:34:17 -04:00
md_5
12941ffe62 Close #471 - sync sqlite operations to guard against deadlocks 2013-07-01 21:25:39 +10:00
md_5
06e732d8c7 Close #474 - 1.5 ping to 1.6 bungee 2013-07-01 21:20:18 +10:00
md_5
5c4ea3c7a0 Solve long standing issue of creating too many TCP packets. This fix works very effectively. 2013-07-01 17:38:50 +10:00
md_5
632fa8bd94 Partially support forced_hosts MOTD without SRV records - gonna think how best to put this in the config, for now its server: motd:, might remove listener motd later on 2013-07-01 14:05:57 +10:00
md_5
8732904bfd Add stream helper to PacketFA 2013-07-01 13:45:36 +10:00
md_5
788b96dc0a knohacks - thanks @ammaraskar 2013-07-01 13:37:03 +10:00
md_5
1296783d9b Update to Minecraft 1.6.1 2013-07-01 13:19:18 +10:00
99 changed files with 2400 additions and 789 deletions

2
.gitignore vendored
View File

@@ -1,7 +1,7 @@
# Eclipse stuff # Eclipse stuff
.classpath .classpath
.project .project
.settings .settings/
# netbeans # netbeans
nbproject/ nbproject/

View File

@@ -6,13 +6,13 @@
<parent> <parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId> <artifactId>bungeecord-parent</artifactId>
<version>1.5-SNAPSHOT</version> <version>1.6.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId> <artifactId>bungeecord-api</artifactId>
<version>1.5-SNAPSHOT</version> <version>1.6.4-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>BungeeCord-API</name> <name>BungeeCord-API</name>
@@ -26,9 +26,9 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.ning</groupId> <groupId>net.md-5</groupId>
<artifactId>async-http-client</artifactId> <artifactId>bungeecord-config</artifactId>
<version>1.7.17</version> <version>${project.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -43,11 +43,5 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.12</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -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);
}

View File

@@ -2,7 +2,6 @@ package net.md_5.bungee.api;
import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.api.plugin.PluginManager;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.ning.http.client.AsyncHttpClient;
import java.io.File; import java.io.File;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Collection; import java.util.Collection;
@@ -54,7 +53,7 @@ public abstract class ProxyServer
* *
* @return the localized string * @return the localized string
*/ */
public abstract String getTranslation(String name); public abstract String getTranslation(String name, Object... args);
/** /**
* Gets the main logger which can be used as a suitable replacement for * Gets the main logger which can be used as a suitable replacement for
@@ -189,10 +188,11 @@ public abstract class ProxyServer
* *
* @param name name of the server * @param name name of the server
* @param address connectable Minecraft address + port of the server * @param address connectable Minecraft address + port of the server
* @param motd the motd when used as a forced server
* @param restricted whether the server info restricted property will be set * @param restricted whether the server info restricted property will be set
* @return the constructed instance * @return the constructed instance
*/ */
public abstract ServerInfo constructServerInfo(String name, InetSocketAddress address, boolean restricted); public abstract ServerInfo constructServerInfo(String name, InetSocketAddress address, String motd, boolean restricted);
/** /**
* Returns the console overlord for this proxy. Being the console, this * Returns the console overlord for this proxy. Being the console, this
@@ -217,15 +217,6 @@ public abstract class ProxyServer
*/ */
public abstract TaskScheduler getScheduler(); public abstract TaskScheduler getScheduler();
/**
* Gets the the web client used by this proxy to facilitate making web
* requests. Care should be taken to ensure that all operations are non
* blocking where applicable.
*
* @return the server's {@link AsyncHttpClient} instance
*/
public abstract AsyncHttpClient getHttpClient();
/** /**
* Get the current number of connected users. The default implementation is * Get the current number of connected users. The default implementation is
* more efficient than {@link #getPlayers()} as it does not take a lock or * more efficient than {@link #getPlayers()} as it does not take a lock or
@@ -249,4 +240,11 @@ public abstract class ProxyServer
* @return a new {@link CustomTabList} instance * @return a new {@link CustomTabList} instance
*/ */
public abstract CustomTabList customTabList(ProxiedPlayer player); 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<String> getDisabledCommands();
} }

View File

@@ -43,6 +43,15 @@ public interface ConfigurationAdapter
*/ */
public boolean getBoolean(String path, boolean def); public boolean getBoolean(String path, boolean def);
/**
* Get a list from the specified path.
*
* @param path the path to retrieve the list form.
* @param def the default value
* @return the retrieved list
*/
public Collection<?> getList(String path, Collection<?> def);
/** /**
* Get the configuration all servers which may be accessible via the proxy. * Get the configuration all servers which may be accessible via the proxy.
* *

View File

@@ -48,13 +48,25 @@ public class ListenerInfo
* transferred depending on the host they connect to. * transferred depending on the host they connect to.
*/ */
private final Map<String, String> forcedHosts; private final Map<String, String> forcedHosts;
/**
* Get the texture pack used for servers connected to this proxy. May be
* null.
*/
private final TexturePackInfo texturePack;
/** /**
* Class used to build tab lists for this player. * Class used to build tab lists for this player.
*/ */
private final Class<? extends TabListHandler> tabList; private final Class<? extends TabListHandler> tabList;
/**
* Whether to set the local address when connecting to servers.
*/
private final boolean setLocalAddress;
/**
* Whether to pass the ping through when we can reliably get the target
* server (force default server).
*/
private final boolean pingPassthrough;
/**
* What port to run udp query on.
*/
private final int queryPort;
/**
* Whether to enable udp query.
*/
private final boolean queryEnabled;
} }

View File

@@ -36,6 +36,13 @@ public interface ServerInfo
*/ */
Collection<ProxiedPlayer> getPlayers(); Collection<ProxiedPlayer> getPlayers();
/**
* Returns the MOTD which should be used when this server is a forced host.
*
* @return the motd
*/
String getMotd();
/** /**
* Whether the player can access this server. It will only return false when * Whether the player can access this server. It will only return false when
* the player has no permission and this server is restricted. * the player has no permission and this server is restricted.

View File

@@ -1,17 +0,0 @@
package net.md_5.bungee.api.config;
import lombok.Data;
@Data
public class TexturePackInfo
{
/**
* The URL of the texture pack.
*/
private final String url;
/**
* The square dimension of this texture pack.
*/
private final int size;
}

View File

@@ -14,26 +14,26 @@ public interface PendingConnection extends Connection
* *
* @return the requested username, or null if not set * @return the requested username, or null if not set
*/ */
public String getName(); String getName();
/** /**
* Get the numerical client version of the player attempting to log in. * Get the numerical client version of the player attempting to log in.
* *
* @return the protocol version of the remote client * @return the protocol version of the remote client
*/ */
public byte getVersion(); byte getVersion();
/** /**
* Get the requested virtual host that the client tried to connect to. * Get the requested virtual host that the client tried to connect to.
* *
* @return request virtual host or null if invalid / not specified. * @return request virtual host or null if invalid / not specified.
*/ */
public InetSocketAddress getVirtualHost(); InetSocketAddress getVirtualHost();
/** /**
* Get the listener that accepted this connection. * Get the listener that accepted this connection.
* *
* @return the accepting listener * @return the accepting listener
*/ */
public ListenerInfo getListener(); ListenerInfo getListener();
} }

View File

@@ -2,7 +2,6 @@ package net.md_5.bungee.api.connection;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.config.ServerInfo; 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.api.tab.TabListHandler;
/** /**
@@ -72,13 +71,6 @@ public interface ProxiedPlayer extends Connection, CommandSender
*/ */
void chat(String message); void chat(String message);
/**
* Send a request to change the players texture pack.
*
* @param pack the pack to request
*/
void setTexturePack(TexturePackInfo pack);
/** /**
* Sets the new tab list for the user. At this stage it is not advisable to * Sets the new tab list for the user. At this stage it is not advisable to
* change after the user has logged in! * change after the user has logged in!
@@ -93,4 +85,18 @@ public interface ProxiedPlayer extends Connection, CommandSender
* @return the tab list in use by this user * @return the tab list in use by this user
*/ */
TabListHandler getTabList(); 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);
} }

View File

@@ -0,0 +1,34 @@
package net.md_5.bungee.api.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
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.
*/
@Data
@ToString(callSuper = false)
@EqualsAndHashCode(callSuper = false)
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;
}
}

View File

@@ -2,6 +2,7 @@ package net.md_5.bungee.api.event;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString; import lombok.ToString;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -21,6 +22,7 @@ public class ServerConnectEvent extends Event implements Cancellable
/** /**
* Server the player will be connected to. * Server the player will be connected to.
*/ */
@NonNull
private ServerInfo target; private ServerInfo target;
/** /**
* Cancelled state. * Cancelled state.

View File

@@ -33,11 +33,27 @@ public class ServerKickEvent extends Event implements Cancellable
* Server to send player to if this event is cancelled. * Server to send player to if this event is cancelled.
*/ */
private ServerInfo cancelServer; private ServerInfo cancelServer;
/**
* State in which the kick occured.
*/
private State state;
public enum State
{
CONNECTING, CONNECTED, UNKNOWN;
}
public ServerKickEvent(ProxiedPlayer player, String kickReason, ServerInfo cancelServer) 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.player = player;
this.kickReason = kickReason; this.kickReason = kickReason;
this.cancelServer = cancelServer; this.cancelServer = cancelServer;
this.state = state;
} }
} }

View File

@@ -2,13 +2,18 @@ package net.md_5.bungee.api.plugin;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class PluginClassloader extends URLClassLoader public class PluginClassloader extends URLClassLoader
{ {
private static final Set<PluginClassloader> allLoaders = new HashSet<>(); private static final Set<PluginClassloader> allLoaders = new CopyOnWriteArraySet<>();
static
{
ClassLoader.registerAsParallelCapable();
}
public PluginClassloader(URL[] urls) public PluginClassloader(URL[] urls)
{ {

View File

@@ -13,13 +13,12 @@ public class PluginLogger extends Logger
{ {
super( plugin.getClass().getCanonicalName(), null ); super( plugin.getClass().getCanonicalName(), null );
pluginName = "[" + plugin.getDescription().getName() + "] "; pluginName = "[" + plugin.getDescription().getName() + "] ";
setParent( ProxyServer.getInstance().getLogger() );
} }
@Override @Override
public void log(LogRecord logRecord) public void log(LogRecord logRecord)
{ {
logRecord.setMessage( pluginName + logRecord.getMessage() ); logRecord.setMessage( pluginName + logRecord.getMessage() );
super.log( logRecord ); ProxyServer.getInstance().getLogger().log( logRecord );
} }
} }

View File

@@ -1,6 +1,8 @@
package net.md_5.bungee.api.plugin; package net.md_5.bungee.api.plugin;
import com.google.common.base.Preconditions; 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 com.google.common.eventbus.Subscribe;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
@@ -10,7 +12,9 @@ import java.net.URLClassLoader;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Stack; import java.util.Stack;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
@@ -42,12 +46,14 @@ public class PluginManager
private final Map<String, Plugin> plugins = new LinkedHashMap<>(); private final Map<String, Plugin> plugins = new LinkedHashMap<>();
private final Map<String, Command> commandMap = new HashMap<>(); private final Map<String, Command> commandMap = new HashMap<>();
private Map<String, PluginDescription> toLoad = new HashMap<>(); private Map<String, PluginDescription> toLoad = new HashMap<>();
private Multimap<Plugin, Command> commandsByPlugin = ArrayListMultimap.create();
private Multimap<Plugin, Listener> listenersByPlugin = ArrayListMultimap.create();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public PluginManager(ProxyServer proxy) public PluginManager(ProxyServer proxy)
{ {
this.proxy = proxy; this.proxy = proxy;
eventBus = new EventBus( proxy.getLogger(), Subscribe.class, EventHandler.class ); eventBus = new EventBus( proxy.getLogger() );
} }
/** /**
@@ -63,6 +69,7 @@ public class PluginManager
{ {
commandMap.put( alias.toLowerCase(), command ); commandMap.put( alias.toLowerCase(), command );
} }
commandsByPlugin.put( plugin, command );
} }
/** /**
@@ -73,6 +80,26 @@ public class PluginManager
public void unregisterCommand(Command command) public void unregisterCommand(Command command)
{ {
commandMap.values().remove( 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<Command> 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 );
} }
/** /**
@@ -83,10 +110,21 @@ public class PluginManager
* arguments * arguments
* @return whether the command was handled * @return whether the command was handled
*/ */
public boolean dispatchCommand(CommandSender sender, String commandLine) public boolean dispatchCommand(CommandSender sender, String commandLine, List<String> tabResults)
{ {
String[] split = argsSplit.split( commandLine ); String[] split = argsSplit.split( commandLine );
Command command = commandMap.get( split[0].toLowerCase() ); // 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 ) if ( command == null )
{ {
return false; return false;
@@ -101,8 +139,17 @@ public class PluginManager
String[] args = Arrays.copyOfRange( split, 1, split.length ); String[] args = Arrays.copyOfRange( split, 1, split.length );
try try
{
if ( tabResults == null )
{ {
command.execute( sender, args ); command.execute( sender, args );
} else if ( command instanceof TabExecutor )
{
for ( String s : ( (TabExecutor) command ).onTabComplete( sender, args ) )
{
tabResults.add( s );
}
}
} catch ( Exception ex ) } catch ( Exception ex )
{ {
sender.sendMessage( ChatColor.RED + "An internal error occurred whilst executing this command, please check the console log for details." ); sender.sendMessage( ChatColor.RED + "An internal error occurred whilst executing this command, please check the console log for details." );
@@ -176,7 +223,7 @@ public class PluginManager
for ( String dependName : plugin.getDepends() ) for ( String dependName : plugin.getDepends() )
{ {
PluginDescription depend = toLoad.get( dependName ); PluginDescription depend = toLoad.get( dependName );
Boolean dependStatus = depend != null ? pluginStatuses.get( depend ) : Boolean.FALSE; Boolean dependStatus = ( depend != null ) ? pluginStatuses.get( depend ) : Boolean.FALSE;
if ( dependStatus == null ) if ( dependStatus == null )
{ {
@@ -202,7 +249,7 @@ public class PluginManager
{ {
ProxyServer.getInstance().getLogger().log( Level.WARNING, "{0} (required by {1}) is unavailable", new Object[] ProxyServer.getInstance().getLogger().log( Level.WARNING, "{0} (required by {1}) is unavailable", new Object[]
{ {
depend.getName(), plugin.getName() String.valueOf( depend.getName() ), plugin.getName()
} ); } );
status = false; status = false;
} }
@@ -305,7 +352,7 @@ public class PluginManager
/** /**
* Register a {@link Listener} for receiving called events. Methods in this * Register a {@link Listener} for receiving called events. Methods in this
* Object which wish to receive events must be annotated with the * Object which wish to receive events must be annotated with the
* {@link Subscribe} annotation. * {@link EventHandler} annotation.
* *
* @param plugin the owning plugin * @param plugin the owning plugin
* @param listener the listener to register events for * @param listener the listener to register events for
@@ -314,14 +361,35 @@ public class PluginManager
{ {
for ( Method method : listener.getClass().getDeclaredMethods() ) for ( Method method : listener.getClass().getDeclaredMethods() )
{ {
if ( method.isAnnotationPresent( Subscribe.class ) ) Preconditions.checkArgument( !method.isAnnotationPresent( Subscribe.class ),
{ "Listener %s has registered using deprecated subscribe annotation! Please update to @EventHandler.", listener );
proxy.getLogger().log( Level.WARNING, "Listener " + listener + " has registered using depreceated subscribe annotation!"
+ " Please advice author to update to @EventHandler."
+ " As a server owner you may safely ignore this.", new Exception() );
} }
eventBus.register( listener );
listenersByPlugin.put( plugin, listener );
} }
eventBus.register( 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<Listener> it = listenersByPlugin.get( plugin ).iterator(); it.hasNext(); )
{
eventBus.unregister( it.next() );
it.remove();
}
} }
} }

View File

@@ -0,0 +1,10 @@
package net.md_5.bungee.api.plugin;
import net.md_5.bungee.api.CommandSender;
public interface TabExecutor
{
public Iterable<String> onTabComplete(CommandSender sender, String[] args);
}

View File

@@ -1,6 +1,5 @@
package net.md_5.bungee.api.scheduler; package net.md_5.bungee.api.scheduler;
import java.util.concurrent.TimeUnit;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
/** /**
@@ -31,11 +30,7 @@ public interface ScheduledTask
Runnable getTask(); Runnable getTask();
/** /**
* Get the delay in the specified unit before this task will next be * Cancel this task to suppress subsequent executions.
* executed.
*
* @param unit the unit to get the delay in
* @return the time before the next execution of this task
*/ */
long getDelay(TimeUnit unit); void cancel();
} }

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-shared-configuration>
<!--
This file contains additional configuration written by modules in the NetBeans IDE.
The configuration is intended to be shared among all the users of project and
therefore it is assumed to be part of version control checkout.
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
-->
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
<!--
Properties that influence various parts of the IDE, especially code formatting and the like.
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
That way multiple projects can share the same settings (useful for formatting rules for example).
Any value defined here will override the pom.xml file value but is only applicable to the current project.
-->
<org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>
</properties>
</project-shared-configuration>

97
bootstrap/pom.xml Normal file
View File

@@ -0,0 +1,97 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.6.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-bootstrap</artifactId>
<version>1.6.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Bootstrap</name>
<description>Java 1.6 loader for BungeeCord</description>
<dependencies>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-proxy</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>
<version>4.5</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>BungeeCord</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<!-- Don't deploy proxy to maven repo, only APIs -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Main-Class>net.md_5.bungee.Bootstrap</Main-Class>
<Implementation-Version>${describe}</Implementation-Version>
<Specification-Version>${maven.build.timestamp}</Specification-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>**/*.java</exclude>
<exclude>**/*.SF</exclude>
<exclude>**/*.DSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,81 @@
package net.md_5.bungee;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.TimeUnit;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
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<String> 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
{
OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();
parser.acceptsAll( list( "v", "version" ) );
OptionSet options = parser.parse( args );
if ( options.has( "version" ) )
{
System.out.println( Bootstrap.class.getPackage().getImplementationVersion() );
return;
}
if ( !System.getProperty( "java.version" ).startsWith( "1.7" ) )
{
System.err.println( "*** ERROR *** BungeeCord requires Java 7 to function!" );
return;
}
if ( BungeeCord.class.getPackage().getSpecificationVersion() != null )
{
Calendar deadline = Calendar.getInstance();
deadline.add( Calendar.WEEK_OF_YEAR, 2 );
if ( Calendar.getInstance().after( new SimpleDateFormat( "yyyyMMdd" ).parse( BungeeCord.class.getPackage().getSpecificationVersion() ) ) )
{
System.err.println( "*** Warning, this build is outdated ***" );
System.err.println( "*** Please download a new build from http://ci.md-5.net/job/BungeeCord ***" );
System.err.println( "*** You will get NO support regarding this build ***" );
System.err.println( "*** Server will start in 30 seconds ***" );
Thread.sleep( TimeUnit.SECONDS.toMillis( 30 ) );
}
}
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" );
}
}
}
}
}

View File

@@ -6,29 +6,24 @@
<parent> <parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId> <artifactId>bungeecord-parent</artifactId>
<version>1.4.7-SNAPSHOT</version> <version>1.6.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-config</artifactId> <artifactId>bungeecord-config</artifactId>
<version>1.4.7-SNAPSHOT</version> <version>1.6.4-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>BungeeCord-Config</name> <name>BungeeCord-Config</name>
<description>Generic java configuration API intended for use with BungeeCord</description> <description>Generic java configuration API intended for use with BungeeCord</description>
<dependencies> <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.yaml</groupId> <groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
<version>1.11</version> <version>1.13</version>
<scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -2,7 +2,7 @@ package net.md_5.bungee.config;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.AccessLevel; import lombok.AccessLevel;
@@ -13,36 +13,53 @@ public final class Configuration
{ {
private static final char SEPARATOR = '.'; private static final char SEPARATOR = '.';
private final Map<String, Object> self; final Map<String, Object> self;
private Map<String, Object> comments = new HashMap<>();
private final Configuration defaults; private final Configuration defaults;
private Map<String, Object> getHolder(String path, Map<String, Object> parent, boolean create) private Configuration getSectionFor(String path)
{
return null;
}
private Object get(String path, Map<String, Object> holder)
{ {
int index = path.indexOf( SEPARATOR ); int index = path.indexOf( SEPARATOR );
String first, second;
if ( index == -1 ) if ( index == -1 )
{ {
second = path; return this;
} else
{
first = path.substring( 0, index );
second = path.substring( index + 1, path.length() );
} }
return null;
String root = path.substring( 0, index );
Object section = self.get( root );
if ( section == null )
{
section = new LinkedHashMap<>();
self.put( root, section );
}
if ( section instanceof Configuration )
{
return (Configuration) section;
}
return new Configuration( (Map) section, ( defaults == null ) ? null : defaults.getSectionFor( path ) );
}
private String getChild(String path)
{
int index = path.indexOf( SEPARATOR );
return ( index == -1 ) ? path : path.substring( index + 1 );
} }
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T get(String path, T def) public <T> T get(String path, T def)
{ {
Object val = get( path, self ); Configuration section = getSectionFor( path );
return ( val != null && val.getClass().isInstance( def ) ) ? (T) val : (T) defaults.get( path ); Object val;
if ( section == this )
{
val = self.get( path );
} else
{
val = section.get( getChild( path ), def );
}
return ( val != null ) ? (T) val : def;
} }
public Object get(String path) public Object get(String path)
@@ -52,19 +69,26 @@ public final class Configuration
public Object getDefault(String path) public Object getDefault(String path)
{ {
return defaults.get( path ); return ( defaults == null ) ? null : defaults.get( path );
}
public void set(String path, Object value, String comment)
{
String child = path.substring( path.indexOf( SEPARATOR ) + 1 );
getHolder( path, self, true ).put( child, value );
getHolder( path, comments, true ).put( child, value );
} }
public void set(String path, Object value) public void set(String path, Object value)
{ {
set( path, value, null ); Configuration section = getSectionFor( path );
if ( section == this )
{
self.put( path, value );
} else
{
section.set( getChild( path ), value );
}
}
/*------------------------------------------------------------------------*/
public Configuration getSection(String path)
{
Object def = getDefault( path );
return new Configuration( (Map) ( get( path, ( def instanceof Map ) ? def : Collections.EMPTY_MAP ) ), ( defaults == null ) ? null : defaults.getSection( path ) );
} }
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/

View File

@@ -1,7 +1,9 @@
package net.md_5.bungee.config; package net.md_5.bungee.config;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.Writer;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -15,13 +17,17 @@ public abstract class ConfigurationProvider
providers.put( YamlConfiguration.class, new YamlConfiguration() ); providers.put( YamlConfiguration.class, new YamlConfiguration() );
} }
public ConfigurationProvider getProvider(Class<? extends ConfigurationProvider> provider) public static ConfigurationProvider getProvider(Class<? extends ConfigurationProvider> provider)
{ {
return providers.get( provider ); return providers.get( provider );
} }
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
public abstract Configuration load(File file); public abstract void save(Configuration config, File file) throws IOException;
public abstract void save(Configuration config, Writer writer);
public abstract Configuration load(File file) throws IOException;
public abstract Configuration load(Reader reader); public abstract Configuration load(Reader reader);

View File

@@ -1,14 +1,18 @@
package net.md_5.bungee.config; package net.md_5.bungee.config;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.Writer;
import java.util.Map; import java.util.Map;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
@NoArgsConstructor(access = AccessLevel.PACKAGE)
public class YamlConfiguration extends ConfigurationProvider public class YamlConfiguration extends ConfigurationProvider
{ {
@@ -24,14 +28,26 @@ public class YamlConfiguration extends ConfigurationProvider
}; };
@Override @Override
public Configuration load(File file) public void save(Configuration config, File file) throws IOException
{
try ( FileWriter writer = new FileWriter( file ) )
{
save( config, writer );
}
}
@Override
public void save(Configuration config, Writer writer)
{
yaml.get().dump( config.self, writer );
}
@Override
public Configuration load(File file) throws IOException
{ {
try ( FileReader reader = new FileReader( file ) ) try ( FileReader reader = new FileReader( file ) )
{ {
return load( reader ); return load( reader );
} catch ( IOException ex )
{
return null;
} }
} }

View File

@@ -0,0 +1,61 @@
package net.md_5.bungee.config;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
public class YamlConfigurationTest
{
private String docuement = ""
+ "receipt: Oz-Ware Purchase Invoice\n"
+ "date: 2012-08-06\n"
+ "customer:\n"
+ " given: Dorothy\n"
+ " family: Gale\n"
+ "\n"
+ "items:\n"
+ " - part_no: A4786\n"
+ " descrip: Water Bucket (Filled)\n"
+ " price: 1.47\n"
+ " quantity: 4\n"
+ "\n"
+ " - part_no: E1628\n"
+ " descrip: High Heeled \"Ruby\" Slippers\n"
+ " size: 8\n"
+ " price: 100.27\n"
+ " quantity: 1\n"
+ "\n"
+ "bill-to: &id001\n"
+ " street: |\n"
+ " 123 Tornado Alley\n"
+ " Suite 16\n"
+ " city: East Centerville\n"
+ " state: KS\n"
+ "\n"
+ "ship-to: *id001\n"
+ "\n"
+ "specialDelivery: >\n"
+ " Follow the Yellow Brick\n"
+ " Road to the Emerald City.\n"
+ " Pay no attention to the\n"
+ " man behind the curtain.";
@Test
public void testRead() throws Exception
{
Configuration conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( docuement );
Assert.assertEquals( "receipt", "Oz-Ware Purchase Invoice", conf.getString( "receipt" ) );
// Assert.assertEquals( "date", "2012-08-06", conf.get( "date" ).toString() );
Configuration customer = conf.getSection( "customer" );
Assert.assertEquals( "customer.given", "Dorothy", customer.getString( "given" ) );
Assert.assertEquals( "customer.given", "Dorothy", conf.getString( "customer.given" ) );
List items = conf.getList( "items" );
Map item = (Map) items.get( 0 );
Assert.assertEquals( "items[0].part_no", "A4786", item.get( "part_no" ) );
}
}

View File

@@ -6,24 +6,15 @@
<parent> <parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId> <artifactId>bungeecord-parent</artifactId>
<version>1.5-SNAPSHOT</version> <version>1.6.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-event</artifactId> <artifactId>bungeecord-event</artifactId>
<version>1.5-SNAPSHOT</version> <version>1.6.4-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>BungeeCord-Event</name> <name>BungeeCord-Event</name>
<description>Generic java event dispatching API intended for use with BungeeCord</description> <description>Generic java event dispatching API intended for use with BungeeCord</description>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
</project> </project>

View File

@@ -1,11 +1,12 @@
package net.md_5.bungee.event; package net.md_5.bungee.event;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
@@ -16,35 +17,19 @@ import java.util.logging.Logger;
public class EventBus public class EventBus
{ {
private final Map<Class<?>, Map<Object, Method[]>> eventToHandler = new HashMap<>(); private final Map<Class<?>, Map<Byte, Map<Object, Method[]>>> byListenerAndPriority = new HashMap<>();
private final Map<Class<?>, EventHandlerMethod[]> byEventBaked = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Logger logger; private final Logger logger;
private final Class<? extends Annotation>[] annotations;
public EventBus() public EventBus()
{ {
this( null, (Class<? extends Annotation>[]) null ); this( null );
} }
public EventBus(Logger logger) public EventBus(Logger logger)
{
this( logger, (Class<? extends Annotation>[]) null );
}
@SuppressWarnings("unchecked")
public EventBus(Class<? extends Annotation>... annotations)
{
this( null, annotations );
}
@SuppressWarnings("unchecked")
public EventBus(Logger logger, Class<? extends Annotation>... annotations)
{ {
this.logger = ( logger == null ) ? Logger.getGlobal() : logger; this.logger = ( logger == null ) ? Logger.getGlobal() : logger;
this.annotations = ( annotations == null || annotations.length == 0 ) ? new Class[]
{
EventHandler.class
} : annotations;
} }
public void post(Object event) public void post(Object event)
@@ -52,16 +37,14 @@ public class EventBus
lock.readLock().lock(); lock.readLock().lock();
try try
{ {
Map<Object, Method[]> handlers = eventToHandler.get( event.getClass() ); EventHandlerMethod[] handlers = byEventBaked.get( event.getClass() );
if ( handlers != null ) if ( handlers != null )
{ {
for ( Map.Entry<Object, Method[]> handler : handlers.entrySet() ) for ( EventHandlerMethod method : handlers )
{
for ( Method method : handler.getValue() )
{ {
try try
{ {
method.invoke( handler.getKey(), event ); method.invoke( event );
} catch ( IllegalAccessException ex ) } catch ( IllegalAccessException ex )
{ {
throw new Error( "Method became inaccessible: " + event, ex ); throw new Error( "Method became inaccessible: " + event, ex );
@@ -70,8 +53,7 @@ public class EventBus
throw new Error( "Method rejected target/argument: " + event, ex ); throw new Error( "Method rejected target/argument: " + event, ex );
} catch ( InvocationTargetException ex ) } catch ( InvocationTargetException ex )
{ {
logger.log( Level.WARNING, MessageFormat.format( "Error dispatching event {0} to listener {1}", event, handler.getKey() ), ex.getCause() ); logger.log( Level.WARNING, MessageFormat.format( "Error dispatching event {0} to listener {1}", event, method.getListener() ), ex.getCause() );
}
} }
} }
} }
@@ -81,14 +63,13 @@ public class EventBus
} }
} }
private Map<Class<?>, Set<Method>> findHandlers(Object listener) private Map<Class<?>, Map<Byte, Set<Method>>> findHandlers(Object listener)
{ {
Map<Class<?>, Set<Method>> handler = new HashMap<>(); Map<Class<?>, Map<Byte, Set<Method>>> handler = new HashMap<>();
for ( Method m : listener.getClass().getDeclaredMethods() ) for ( Method m : listener.getClass().getDeclaredMethods() )
{ {
for ( Class<? extends Annotation> annotation : annotations ) EventHandler annotation = m.getAnnotation( EventHandler.class );
{ if ( annotation != null )
if ( m.isAnnotationPresent( annotation ) )
{ {
Class<?>[] params = m.getParameterTypes(); Class<?>[] params = m.getParameterTypes();
if ( params.length != 1 ) if ( params.length != 1 )
@@ -99,16 +80,19 @@ public class EventBus
} ); } );
continue; continue;
} }
Map<Byte, Set<Method>> prioritiesMap = handler.get( params[0] );
Set<Method> existing = handler.get( params[0] ); if ( prioritiesMap == null )
if ( existing == null )
{ {
existing = new HashSet<>(); prioritiesMap = new HashMap<>();
handler.put( params[0], existing ); handler.put( params[0], prioritiesMap );
} }
existing.add( m ); Set<Method> priority = prioritiesMap.get( annotation.priority() );
break; if ( priority == null )
{
priority = new HashSet<>();
prioritiesMap.put( annotation.priority(), priority );
} }
priority.add( m );
} }
} }
return handler; return handler;
@@ -116,20 +100,30 @@ public class EventBus
public void register(Object listener) public void register(Object listener)
{ {
Map<Class<?>, Set<Method>> handler = findHandlers( listener ); Map<Class<?>, Map<Byte, Set<Method>>> handler = findHandlers( listener );
lock.writeLock().lock(); lock.writeLock().lock();
try try
{ {
for ( Map.Entry<Class<?>, Set<Method>> e : handler.entrySet() ) for ( Map.Entry<Class<?>, Map<Byte, Set<Method>>> e : handler.entrySet() )
{ {
Map<Object, Method[]> a = eventToHandler.get( e.getKey() ); Map<Byte, Map<Object, Method[]>> prioritiesMap = byListenerAndPriority.get( e.getKey() );
if ( a == null ) if ( prioritiesMap == null )
{ {
a = new HashMap<>(); prioritiesMap = new HashMap<>();
eventToHandler.put( e.getKey(), a ); byListenerAndPriority.put( e.getKey(), prioritiesMap );
} }
Method[] baked = new Method[ e.getValue().size() ]; for ( Map.Entry<Byte, Set<Method>> entry : e.getValue().entrySet() )
a.put( listener, e.getValue().toArray( baked ) ); {
Map<Object, Method[]> 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 } finally
{ {
@@ -139,25 +133,70 @@ public class EventBus
public void unregister(Object listener) public void unregister(Object listener)
{ {
Map<Class<?>, Set<Method>> handler = findHandlers( listener ); Map<Class<?>, Map<Byte, Set<Method>>> handler = findHandlers( listener );
lock.writeLock().lock(); lock.writeLock().lock();
try try
{ {
for ( Map.Entry<Class<?>, Set<Method>> e : handler.entrySet() ) for ( Map.Entry<Class<?>, Map<Byte, Set<Method>>> e : handler.entrySet() )
{ {
Map<Object, Method[]> a = eventToHandler.get( e.getKey() ); Map<Byte, Map<Object, Method[]>> prioritiesMap = byListenerAndPriority.get( e.getKey() );
if ( a != null ) if ( prioritiesMap != null )
{ {
a.remove( listener ); for ( Byte priority : e.getValue().keySet() )
if ( a.isEmpty() )
{ {
eventToHandler.remove( e.getKey() ); Map<Object, Method[]> 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 } finally
{ {
lock.writeLock().unlock(); 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<Byte, Map<Object, Method[]>> handlersByPriority = byListenerAndPriority.get( eventClass );
if ( handlersByPriority != null )
{
List<EventHandlerMethod> handlersList = new ArrayList<>( handlersByPriority.size() * 2 );
for ( byte value = Byte.MIN_VALUE; value < Byte.MAX_VALUE; value++ )
{
Map<Object, Method[]> handlersByListener = handlersByPriority.get( value );
if ( handlersByListener != null )
{
for ( Map.Entry<Object, Method[]> 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 );
}
}
} }

View File

@@ -9,4 +9,18 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
public @interface EventHandler public @interface EventHandler
{ {
/**
* Define the priority of the event handler.
* <p>
* Event handlers are called in order of priority:
* <ol>
* <li>LOWEST</li>
* <li>LOW</li>
* <li>NORMAL</li>
* <li>HIGH</li>
* <li>HIGHEST</li>
* </ol>
*/
byte priority() default EventPriority.NORMAL;
} }

View File

@@ -0,0 +1,21 @@
package net.md_5.bungee.event;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
public class EventHandlerMethod
{
@Getter
private final Object listener;
@Getter
private final Method method;
public void invoke(Object event) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
method.invoke( listener, event );
}
}

View File

@@ -0,0 +1,19 @@
package net.md_5.bungee.event;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* Importance of the {@link EventHandler}. When executing an Event, the handlers
* are called in order of their Priority.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class 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;
}

View File

@@ -15,14 +15,14 @@ public class EventBusTest
{ {
bus.register( this ); bus.register( this );
bus.post( new FirstEvent() ); bus.post( new FirstEvent() );
Assert.assertEquals( latch.getCount(), 0 ); Assert.assertEquals( 0, latch.getCount() );
} }
@EventHandler @EventHandler
public void firstListener(FirstEvent event) public void firstListener(FirstEvent event)
{ {
bus.post( new SecondEvent() ); bus.post( new SecondEvent() );
Assert.assertEquals( latch.getCount(), 1 ); Assert.assertEquals( 1, latch.getCount() );
latch.countDown(); latch.countDown();
} }

View File

@@ -0,0 +1,64 @@
package net.md_5.bungee.event;
import java.util.concurrent.CountDownLatch;
import org.junit.Assert;
import org.junit.Test;
public class EventPriorityTest
{
private final EventBus bus = new EventBus();
private final CountDownLatch latch = new CountDownLatch( 5 );
@Test
public void testPriority()
{
bus.register( this );
bus.register( new EventPriorityListenerPartner() );
bus.post( new PriorityTestEvent() );
Assert.assertEquals( 0, latch.getCount() );
}
@EventHandler(priority = EventPriority.LOWEST)
public void onLowestPriority(PriorityTestEvent event)
{
Assert.assertEquals( 5, latch.getCount() );
latch.countDown();
}
@EventHandler
public void onNormalPriority(PriorityTestEvent event)
{
Assert.assertEquals( 3, latch.getCount() );
latch.countDown();
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onHighestPriority(PriorityTestEvent event)
{
Assert.assertEquals( 1, latch.getCount() );
latch.countDown();
}
public static class PriorityTestEvent
{
}
public class EventPriorityListenerPartner
{
@EventHandler(priority = EventPriority.HIGH)
public void onHighPriority(PriorityTestEvent event)
{
Assert.assertEquals( 2, latch.getCount() );
latch.countDown();
}
@EventHandler(priority = EventPriority.LOW)
public void onLowPriority(PriorityTestEvent event)
{
Assert.assertEquals( 4, latch.getCount() );
latch.countDown();
}
}
}

View File

@@ -0,0 +1,28 @@
package net.md_5.bungee.event;
import org.junit.Assert;
import org.junit.Test;
public class UnregisteringListenerTest
{
private final EventBus bus = new EventBus();
@Test
public void testPriority()
{
bus.register( this );
bus.unregister( this );
bus.post( new TestEvent() );
}
@EventHandler
public void onEvent(TestEvent evt)
{
Assert.fail( "Event listener wasn't unregistered" );
}
public static class TestEvent
{
}
}

21
pom.xml
View File

@@ -11,16 +11,16 @@
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId> <artifactId>bungeecord-parent</artifactId>
<version>1.5-SNAPSHOT</version> <version>1.6.4-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>BungeeCord</name> <name>BungeeCord</name>
<description>Parent project for all BungeeCord modules.</description> <description>Parent project for all BungeeCord modules.</description>
<url>https://github.com/ElasticPortalSuite/BungeeCord</url> <url>https://github.com/SpigotMC/BungeeCord</url>
<inceptionYear>2012</inceptionYear> <inceptionYear>2012</inceptionYear>
<organization> <organization>
<name>Elastic Portal Suite</name> <name>Elastic Portal Suite</name>
<url>https://github.com/ElasticPortalSuite</url> <url>https://github.com/SpigotMC</url>
</organization> </organization>
<licenses> <licenses>
<license> <license>
@@ -38,19 +38,22 @@
<modules> <modules>
<module>api</module> <module>api</module>
<module>bootstrap</module>
<module>config</module>
<module>event</module> <module>event</module>
<module>protocol</module> <module>protocol</module>
<module>proxy</module> <module>proxy</module>
<module>query</module>
</modules> </modules>
<scm> <scm>
<connection>scm:git:git@github.com:ElasticPortalSuite/BungeeCord.git</connection> <connection>scm:git:git@github.com:SpigotMC/BungeeCord.git</connection>
<developerConnection>scm:git:git@github.com:ElasticPortalSuite/BungeeCord.git</developerConnection> <developerConnection>scm:git:git@github.com:SpigotMC/BungeeCord.git</developerConnection>
<url>git@github.com:ElasticPortalSuite/BungeeCord.git</url> <url>git@github.com:SpigotMC/BungeeCord.git</url>
</scm> </scm>
<issueManagement> <issueManagement>
<system>GitHub</system> <system>GitHub</system>
<url>https://github.com/ElasticPortalSuite/BungeeCord/issues</url> <url>https://github.com/SpigotMC/BungeeCord/issues</url>
</issueManagement> </issueManagement>
<ciManagement> <ciManagement>
<system>jenkins</system> <system>jenkins</system>
@@ -59,7 +62,7 @@
<properties> <properties>
<build.number>unknown</build.number> <build.number>unknown</build.number>
<netty.version>4.0.0.CR7</netty.version> <netty.version>4.0.9.Final</netty.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
@@ -73,7 +76,7 @@
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>0.11.8</version> <version>0.12.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -6,13 +6,13 @@
<parent> <parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId> <artifactId>bungeecord-parent</artifactId>
<version>1.5-SNAPSHOT</version> <version>1.6.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-protocol</artifactId> <artifactId>bungeecord-protocol</artifactId>
<version>1.5-SNAPSHOT</version> <version>1.6.4-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>BungeeCord-Protocol</name> <name>BungeeCord-Protocol</name>
@@ -23,7 +23,7 @@
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId> <artifactId>netty-buffer</artifactId>
<version>${netty.version}</version> <version>${netty.version}</version>
<scope>provided</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -0,0 +1,10 @@
package net.md_5.bungee.protocol;
public class BadPacketException extends RuntimeException
{
public BadPacketException(String message)
{
super( message );
}
}

View File

@@ -0,0 +1,38 @@
package net.md_5.bungee.protocol;
import io.netty.buffer.ByteBuf;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class MinecraftInput
{
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 );
}
}

View File

@@ -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" ) ) );
}
}

View File

@@ -3,5 +3,5 @@ package net.md_5.bungee.protocol;
public enum OpCode 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 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
} }

View File

@@ -1,7 +1,6 @@
package net.md_5.bungee.protocol; package net.md_5.bungee.protocol;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import lombok.Getter; import lombok.Getter;
@@ -9,10 +8,12 @@ import static net.md_5.bungee.protocol.OpCode.*;
import net.md_5.bungee.protocol.packet.DefinedPacket; import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.protocol.packet.Packet0KeepAlive; import net.md_5.bungee.protocol.packet.Packet0KeepAlive;
import net.md_5.bungee.protocol.packet.Packet1Login; 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.Packet2Handshake;
import net.md_5.bungee.protocol.packet.Packet3Chat; import net.md_5.bungee.protocol.packet.Packet3Chat;
import net.md_5.bungee.protocol.packet.Packet9Respawn; import net.md_5.bungee.protocol.packet.Packet9Respawn;
import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem; 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.PacketCCSettings;
import net.md_5.bungee.protocol.packet.PacketCDClientStatus; import net.md_5.bungee.protocol.packet.PacketCDClientStatus;
import net.md_5.bungee.protocol.packet.PacketCEScoreboardObjective; import net.md_5.bungee.protocol.packet.PacketCEScoreboardObjective;
@@ -29,8 +30,8 @@ import net.md_5.bungee.protocol.skip.PacketReader;
public class Vanilla implements Protocol public class Vanilla implements Protocol
{ {
public static final byte PROTOCOL_VERSION = 61; public static final byte PROTOCOL_VERSION = 78;
public static final String GAME_VERSION = "1.5.2"; public static final String GAME_VERSION = "1.6.4";
@Getter @Getter
private static final Vanilla instance = new Vanilla(); private static final Vanilla instance = new Vanilla();
/*========================================================================*/ /*========================================================================*/
@@ -54,7 +55,9 @@ public class Vanilla implements Protocol
classes[0x03] = Packet3Chat.class; classes[0x03] = Packet3Chat.class;
classes[0x09] = Packet9Respawn.class; classes[0x09] = Packet9Respawn.class;
classes[0xC9] = PacketC9PlayerListItem.class; classes[0xC9] = PacketC9PlayerListItem.class;
classes[0x2C] = Packet2CEntityProperties.class;
classes[0xCC] = PacketCCSettings.class; classes[0xCC] = PacketCCSettings.class;
classes[0xCB] = PacketCBTabComplete.class;
classes[0xCD] = PacketCDClientStatus.class; classes[0xCD] = PacketCDClientStatus.class;
classes[0xCE] = PacketCEScoreboardObjective.class; classes[0xCE] = PacketCEScoreboardObjective.class;
classes[0xCF] = PacketCFScoreboardScore.class; classes[0xCF] = PacketCFScoreboardScore.class;
@@ -75,7 +78,7 @@ public class Vanilla implements Protocol
DefinedPacket packet = read( packetId, buf, this ); DefinedPacket packet = read( packetId, buf, this );
if ( buf.readerIndex() == start ) if ( buf.readerIndex() == start )
{ {
throw new RuntimeException( "Unknown packet id " + packetId ); throw new BadPacketException( "Unknown packet id " + packetId );
} }
return packet; return packet;
} }
@@ -141,7 +144,7 @@ public class Vanilla implements Protocol
}; };
opCodes[0x08] = new OpCode[] opCodes[0x08] = new OpCode[]
{ {
SHORT, SHORT, FLOAT FLOAT, SHORT, FLOAT
}; };
opCodes[0x0A] = new OpCode[] opCodes[0x0A] = new OpCode[]
{ {
@@ -181,7 +184,7 @@ public class Vanilla implements Protocol
}; };
opCodes[0x13] = new OpCode[] opCodes[0x13] = new OpCode[]
{ {
INT, BYTE INT, BYTE, INT
}; };
opCodes[0x14] = new OpCode[] opCodes[0x14] = new OpCode[]
{ {
@@ -207,6 +210,10 @@ public class Vanilla implements Protocol
{ {
INT, INT, INT, INT, SHORT INT, INT, INT, INT, SHORT
}; };
opCodes[0x1B] = new OpCode[]
{
FLOAT, FLOAT, BOOLEAN, BOOLEAN
};
opCodes[0x1C] = new OpCode[] opCodes[0x1C] = new OpCode[]
{ {
INT, SHORT, SHORT, SHORT INT, SHORT, SHORT, SHORT
@@ -245,7 +252,7 @@ public class Vanilla implements Protocol
}; };
opCodes[0x27] = new OpCode[] opCodes[0x27] = new OpCode[]
{ {
INT, INT INT, INT, BOOLEAN
}; };
opCodes[0x28] = new OpCode[] opCodes[0x28] = new OpCode[]
{ {
@@ -313,7 +320,7 @@ public class Vanilla implements Protocol
}; };
opCodes[0x64] = new OpCode[] opCodes[0x64] = new OpCode[]
{ {
BYTE, BYTE, STRING, BYTE, BOOLEAN OPTIONAL_WINDOW
}; };
opCodes[0x65] = new OpCode[] opCodes[0x65] = new OpCode[]
{ {
@@ -359,21 +366,21 @@ public class Vanilla implements Protocol
{ {
INT, SHORT, INT, BYTE, SHORT_BYTE INT, SHORT, INT, BYTE, SHORT_BYTE
}; };
opCodes[0x85] = new OpCode[]
{
BYTE, INT, INT, INT
};
opCodes[0xC3] = new OpCode[] opCodes[0xC3] = new OpCode[]
{ {
SHORT, SHORT, INT_BYTE SHORT, SHORT, INT_BYTE
}; };
opCodes[0xC8] = new OpCode[] opCodes[0xC8] = new OpCode[]
{ {
INT, BYTE INT, INT
}; };
opCodes[0xCA] = new OpCode[] opCodes[0xCA] = new OpCode[]
{ {
BYTE, BYTE, BYTE BYTE, FLOAT, FLOAT
};
opCodes[0xCB] = new OpCode[]
{
STRING
}; };
} }
} }

View File

@@ -23,6 +23,10 @@ public abstract class AbstractPacketHandler
{ {
} }
public void handle(Packet2CEntityProperties properties) throws Exception
{
}
public void handle(PacketC9PlayerListItem playerList) throws Exception public void handle(PacketC9PlayerListItem playerList) throws Exception
{ {
} }
@@ -70,4 +74,8 @@ public abstract class AbstractPacketHandler
public void handle(PacketFFKick kick) throws Exception public void handle(PacketFFKick kick) throws Exception
{ {
} }
public void handle(PacketCBTabComplete tabComplete) throws Exception
{
}
} }

View File

@@ -0,0 +1,47 @@
package net.md_5.bungee.protocol.packet;
import io.netty.buffer.ByteBuf;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
@EqualsAndHashCode(callSuper = false)
public class Packet2CEntityProperties extends DefinedPacket
{
public Packet2CEntityProperties()
{
super( 0x2C );
}
@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 );
}
}

View File

@@ -3,15 +3,17 @@ package net.md_5.bungee.protocol.packet;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.ToString; import lombok.ToString;
@Getter @Getter
@Setter
@ToString @ToString
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class Packet2Handshake extends DefinedPacket public class Packet2Handshake extends DefinedPacket
{ {
private byte procolVersion; private byte protocolVersion;
private String username; private String username;
private String host; private String host;
private int port; private int port;
@@ -24,7 +26,7 @@ public class Packet2Handshake extends DefinedPacket
@Override @Override
public void read(ByteBuf buf) public void read(ByteBuf buf)
{ {
procolVersion = buf.readByte(); protocolVersion = buf.readByte();
username = readString( buf ); username = readString( buf );
host = readString( buf ); host = readString( buf );
port = buf.readInt(); port = buf.readInt();
@@ -33,7 +35,7 @@ public class Packet2Handshake extends DefinedPacket
@Override @Override
public void write(ByteBuf buf) public void write(ByteBuf buf)
{ {
buf.writeByte( procolVersion ); buf.writeByte( protocolVersion );
writeString( username, buf ); writeString( username, buf );
writeString( host, buf ); writeString( host, buf );
buf.writeInt( port ); buf.writeInt( port );

View File

@@ -3,9 +3,11 @@ package net.md_5.bungee.protocol.packet;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.ToString; import lombok.ToString;
@Getter @Getter
@Setter
@ToString @ToString
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class Packet3Chat extends DefinedPacket public class Packet3Chat extends DefinedPacket

View File

@@ -0,0 +1,51 @@
package net.md_5.bungee.protocol.packet;
import io.netty.buffer.ByteBuf;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
@EqualsAndHashCode(callSuper = false)
public class PacketCBTabComplete extends DefinedPacket
{
private String cursor;
private String[] commands;
private PacketCBTabComplete()
{
super( 0xCB );
}
public PacketCBTabComplete(String[] alternatives)
{
this();
commands = alternatives;
}
@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 );
}
}

View File

@@ -1,9 +1,15 @@
package net.md_5.bungee.protocol.packet; package net.md_5.bungee.protocol.packet;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString; import lombok.ToString;
import net.md_5.bungee.protocol.MinecraftInput;
import net.md_5.bungee.protocol.MinecraftOutput;
@Getter @Getter
@ToString @ToString
@@ -45,4 +51,14 @@ public class PacketFAPluginMessage extends DefinedPacket
{ {
handler.handle( this ); handler.handle( this );
} }
public DataInput getStream()
{
return new DataInputStream( new ByteArrayInputStream( data ) );
}
public MinecraftInput getMCStream()
{
return new MinecraftInput( Unpooled.wrappedBuffer( data ) );
}
} }

View File

@@ -23,6 +23,7 @@ abstract class Instruction
static final Instruction SHORT_ITEM = new ShortHeader( ITEM ); static final Instruction SHORT_ITEM = new ShortHeader( ITEM );
static final Instruction STRING = new ShortHeader( new Jump( 2 ) ); static final Instruction STRING = new ShortHeader( new Jump( 2 ) );
static final Instruction USHORT_BYTE = new UnsignedShortByte(); static final Instruction USHORT_BYTE = new UnsignedShortByte();
static final Instruction OPTIONAL_WINDOW = new OptionalWindow();
// Illegal forward references below this line // Illegal forward references below this line
static final Instruction BYTE_INT = new ByteHeader( INT ); static final Instruction BYTE_INT = new ByteHeader( INT );
// Custom instructions // Custom instructions

View File

@@ -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 );
}
}
}

View File

@@ -6,28 +6,32 @@
<parent> <parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId> <artifactId>bungeecord-parent</artifactId>
<version>1.5-SNAPSHOT</version> <version>1.6.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-proxy</artifactId> <artifactId>bungeecord-proxy</artifactId>
<version>1.5-SNAPSHOT</version> <version>1.6.4-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>BungeeCord-Proxy</name> <name>BungeeCord-Proxy</name>
<description>Proxy component of the Elastic Portal Suite</description> <description>Proxy component of the Elastic Portal Suite</description>
<properties>
<maven.build.timestamp.format>yyyyMMdd</maven.build.timestamp.format>
</properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>netty-codec</artifactId> <artifactId>gson</artifactId>
<version>${netty.version}</version> <version>2.2.4</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId> <artifactId>netty-codec-http</artifactId>
<version>${netty.version}</version> <version>${netty.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -37,6 +41,12 @@
<version>2.11</version> <version>2.11</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-protocol</artifactId> <artifactId>bungeecord-protocol</artifactId>
@@ -45,7 +55,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId> <artifactId>bungeecord-query</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
@@ -58,7 +68,7 @@
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>5.1.24</version> <version>5.1.25</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -67,16 +77,15 @@
<version>3.18.0-GA</version> <version>3.18.0-GA</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.7.2</version>
<scope>runtime</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<finalName>BungeeCord</finalName> <resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins> <plugins>
<plugin> <plugin>
<!-- Don't deploy proxy to maven repo, only APIs --> <!-- Don't deploy proxy to maven repo, only APIs -->
@@ -86,44 +95,6 @@
<skip>true</skip> <skip>true</skip>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Main-Class>net.md_5.bungee.BungeeCord</Main-Class>
<Implementation-Version>${describe}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>**/*.java</exclude>
<exclude>**/*.SF</exclude>
<exclude>**/*.DSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@@ -2,13 +2,10 @@ package net.md_5.bungee;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import net.md_5.bungee.log.BungeeLogger; import net.md_5.bungee.log.BungeeLogger;
import net.md_5.bungee.reconnect.SQLReconnectHandler; import net.md_5.bungee.reconnect.YamlReconnectHandler;
import net.md_5.bungee.scheduler.BungeeScheduler; import net.md_5.bungee.scheduler.BungeeScheduler;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.ning.http.client.AsyncHttpClient; import com.google.gson.Gson;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.providers.netty.NettyAsyncHttpProvider;
import com.ning.http.client.providers.netty.NettyAsyncHttpProviderConfig;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelException; import io.netty.channel.ChannelException;
@@ -17,11 +14,14 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.MultithreadEventLoopGroup; import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.ResourceLeakDetector;
import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.Configuration;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -31,7 +31,6 @@ import java.util.MissingResourceException;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -53,7 +52,6 @@ import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager; 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.api.tab.CustomTabList;
import net.md_5.bungee.command.*; import net.md_5.bungee.command.*;
import net.md_5.bungee.config.YamlConfig; import net.md_5.bungee.config.YamlConfig;
@@ -63,7 +61,7 @@ import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.protocol.packet.Packet3Chat; import net.md_5.bungee.protocol.packet.Packet3Chat;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.protocol.Vanilla; import net.md_5.bungee.protocol.Vanilla;
import net.md_5.bungee.scheduler.BungeeThreadPool; import net.md_5.bungee.query.RemoteQuery;
import net.md_5.bungee.tab.Custom; import net.md_5.bungee.tab.Custom;
import net.md_5.bungee.util.CaseInsensitiveMap; import net.md_5.bungee.util.CaseInsensitiveMap;
import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiConsole;
@@ -85,12 +83,8 @@ public class BungeeCord extends ProxyServer
/** /**
* Localization bundle. * Localization bundle.
*/ */
public final ResourceBundle bundle = ResourceBundle.getBundle( "messages_en" ); public final ResourceBundle bundle = ResourceBundle.getBundle( "messages" );
/** public final MultithreadEventLoopGroup eventLoops = new NioEventLoopGroup( 0, new ThreadFactoryBuilder().setNameFormat( "Netty IO Thread #%1$d" ).build() );
* Thread pools.
*/
public final ScheduledThreadPoolExecutor executors = new BungeeThreadPool( new ThreadFactoryBuilder().setNameFormat( "Bungee Pool Thread #%1$d" ).build() );
public final MultithreadEventLoopGroup eventLoops = new NioEventLoopGroup( Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder().setNameFormat( "Netty IO Thread #%1$d" ).build() );
/** /**
* locations.yml save thread. * locations.yml save thread.
*/ */
@@ -120,16 +114,14 @@ public class BungeeCord extends ProxyServer
@Getter @Getter
private final File pluginsFolder = new File( "plugins" ); private final File pluginsFolder = new File( "plugins" );
@Getter @Getter
private final TaskScheduler scheduler = new BungeeScheduler(); private final BungeeScheduler scheduler = new BungeeScheduler();
@Getter
private final AsyncHttpClient httpClient = new AsyncHttpClient(
new NettyAsyncHttpProvider(
new AsyncHttpClientConfig.Builder().setAsyncHttpClientProviderConfig(
new NettyAsyncHttpProviderConfig().addProperty( NettyAsyncHttpProviderConfig.BOSS_EXECUTOR_SERVICE, executors ) ).setExecutorService( executors ).build() ) );
@Getter @Getter
private ConsoleReader consoleReader; private ConsoleReader consoleReader;
@Getter @Getter
private final Logger logger; private final Logger logger;
public final Gson gson = new Gson();
@Getter
private ConnectionThrottle connectionThrottle;
{ {
@@ -170,43 +162,6 @@ public class BungeeCord extends ProxyServer
} }
} }
/**
* 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
{
Calendar deadline = Calendar.getInstance();
deadline.set( 2013, 7, 1 ); // year, month, date
if ( Calendar.getInstance().after( deadline ) )
{
System.err.println( "*** Warning, this build is outdated ***" );
System.err.println( "*** Please download a new build from http://ci.md-5.net/job/BungeeCord ***" );
System.err.println( "*** You will get NO support regarding this build ***" );
System.err.println( "*** Server will start in 15 seconds ***" );
Thread.sleep( TimeUnit.SECONDS.toMillis( 15 ) );
}
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" );
}
}
}
}
/** /**
* Start this proxy instance by loading the configuration, plugins and * Start this proxy instance by loading the configuration, plugins and
* starting the connect thread. * starting the connect thread.
@@ -216,26 +171,36 @@ public class BungeeCord extends ProxyServer
@Override @Override
public void start() throws Exception public void start() throws Exception
{ {
ResourceLeakDetector.setEnabled( false ); // Eats performance
pluginsFolder.mkdir(); pluginsFolder.mkdir();
pluginManager.detectPlugins( pluginsFolder ); pluginManager.detectPlugins( pluginsFolder );
config.load(); config.load();
if ( reconnectHandler == null ) for ( ListenerInfo info : config.getListeners() )
{ {
reconnectHandler = new SQLReconnectHandler(); if ( !info.isForceDefault() && reconnectHandler == null )
{
reconnectHandler = new YamlReconnectHandler();
break;
}
} }
isRunning = true; isRunning = true;
pluginManager.loadAndEnablePlugins(); pluginManager.loadAndEnablePlugins();
connectionThrottle = new ConnectionThrottle( config.getThrottle() );
startListeners(); startListeners();
saveThread.scheduleAtFixedRate( new TimerTask() saveThread.scheduleAtFixedRate( new TimerTask()
{ {
@Override @Override
public void run() public void run()
{
if ( getReconnectHandler() != null )
{ {
getReconnectHandler().save(); getReconnectHandler().save();
} }
}
}, 0, TimeUnit.MINUTES.toMillis( 5 ) ); }, 0, TimeUnit.MINUTES.toMillis( 5 ) );
metricsThread.scheduleAtFixedRate( new Metrics(), 0, TimeUnit.MINUTES.toMillis( Metrics.PING_INTERVAL ) ); metricsThread.scheduleAtFixedRate( new Metrics(), 0, TimeUnit.MINUTES.toMillis( Metrics.PING_INTERVAL ) );
} }
@@ -266,6 +231,26 @@ public class BungeeCord extends ProxyServer
.group( eventLoops ) .group( eventLoops )
.localAddress( info.getHost() ) .localAddress( info.getHost() )
.bind().addListener( listener ); .bind().addListener( listener );
if ( info.isQueryEnabled() )
{
ChannelFutureListener bindListener = new ChannelFutureListener()
{
@Override
public void operationComplete(ChannelFuture future) throws Exception
{
if ( future.isSuccess() )
{
listeners.add( future.channel() );
getLogger().info( "Started query on " + future.channel().localAddress() );
} else
{
getLogger().log( Level.WARNING, "Could not bind to host " + future.channel().remoteAddress(), future.cause() );
}
}
};
new RemoteQuery( this, info ).start( new InetSocketAddress( info.getHost().getAddress(), info.getQueryPort() ), eventLoops, bindListener );
}
} }
} }
@@ -295,9 +280,6 @@ public class BungeeCord extends ProxyServer
{ {
BungeeCord.this.isRunning = false; BungeeCord.this.isRunning = false;
httpClient.close();
executors.shutdown();
stopListeners(); stopListeners();
getLogger().info( "Closing pending connections" ); getLogger().info( "Closing pending connections" );
@@ -323,20 +305,31 @@ public class BungeeCord extends ProxyServer
{ {
} }
if ( reconnectHandler != null )
{
getLogger().info( "Saving reconnect locations" ); getLogger().info( "Saving reconnect locations" );
reconnectHandler.save(); reconnectHandler.save();
reconnectHandler.close(); reconnectHandler.close();
}
saveThread.cancel(); saveThread.cancel();
metricsThread.cancel(); metricsThread.cancel();
// TODO: Fix this shit // TODO: Fix this shit
getLogger().info( "Disabling plugins" ); getLogger().info( "Disabling plugins" );
for ( Plugin plugin : pluginManager.getPlugins() ) for ( Plugin plugin : pluginManager.getPlugins() )
{
try
{ {
plugin.onDisable(); plugin.onDisable();
} catch ( Throwable t )
{
getLogger().severe( "Exception disabling plugin " + plugin.getDescription().getName() );
t.printStackTrace();
}
getScheduler().cancel( plugin ); getScheduler().cancel( plugin );
} }
scheduler.shutdown();
getLogger().info( "Thankyou and goodbye" ); getLogger().info( "Thankyou and goodbye" );
System.exit( 0 ); System.exit( 0 );
} }
@@ -376,12 +369,12 @@ public class BungeeCord extends ProxyServer
} }
@Override @Override
public String getTranslation(String name) public String getTranslation(String name, Object... args)
{ {
String translation = "<translation '" + name + "' missing>"; String translation = "<translation '" + name + "' missing>";
try try
{ {
translation = bundle.getString( name ); translation = MessageFormat.format( bundle.getString( name ), args );
} catch ( MissingResourceException ex ) } catch ( MissingResourceException ex )
{ {
} }
@@ -472,9 +465,9 @@ public class BungeeCord extends ProxyServer
} }
@Override @Override
public ServerInfo constructServerInfo(String name, InetSocketAddress address, boolean restricted) public ServerInfo constructServerInfo(String name, InetSocketAddress address, String motd, boolean restricted)
{ {
return new BungeeServerInfo( name, address, restricted ); return new BungeeServerInfo( name, address, motd, restricted );
} }
@Override @Override
@@ -487,7 +480,9 @@ public class BungeeCord extends ProxyServer
public void broadcast(String message) public void broadcast(String message)
{ {
getConsole().sendMessage( message ); getConsole().sendMessage( message );
broadcast( new Packet3Chat( message ) ); // TODO: Here too
String encoded = BungeeCord.getInstance().gson.toJson( message );
broadcast( new Packet3Chat( "{\"text\":" + encoded + "}" ) );
} }
public void addConnection(UserConnection con) public void addConnection(UserConnection con)
@@ -519,4 +514,9 @@ public class BungeeCord extends ProxyServer
{ {
return new Custom( player ); return new Custom( player );
} }
public Collection<String> getDisabledCommands()
{
return config.getDisabledCommands();
}
} }

View File

@@ -38,6 +38,8 @@ public class BungeeServerInfo implements ServerInfo
private final InetSocketAddress address; private final InetSocketAddress address;
private final Collection<ProxiedPlayer> players = new ArrayList<>(); private final Collection<ProxiedPlayer> players = new ArrayList<>();
@Getter @Getter
private final String motd;
@Getter
private final boolean restricted; private final boolean restricted;
@Getter @Getter
private final Queue<DefinedPacket> packetQueue = new LinkedList<>(); private final Queue<DefinedPacket> packetQueue = new LinkedList<>();
@@ -87,13 +89,13 @@ public class BungeeServerInfo implements ServerInfo
Preconditions.checkNotNull( channel, "channel" ); Preconditions.checkNotNull( channel, "channel" );
Preconditions.checkNotNull( data, "data" ); Preconditions.checkNotNull( data, "data" );
synchronized ( packetQueue )
{
Server server = ( players.isEmpty() ) ? null : players.iterator().next().getServer(); Server server = ( players.isEmpty() ) ? null : players.iterator().next().getServer();
if ( server != null ) if ( server != null )
{ {
server.sendData( channel, data ); server.sendData( channel, data );
} else } else
{
synchronized ( packetQueue )
{ {
packetQueue.add( new PacketFAPluginMessage( channel, data ) ); packetQueue.add( new PacketFAPluginMessage( channel, data ) );
} }

View File

@@ -0,0 +1,28 @@
package net.md_5.bungee;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class ConnectionThrottle
{
private final Map<InetAddress, Long> throttle = new HashMap<>();
private final int 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;
}
}

View File

@@ -40,9 +40,9 @@ public class EncryptionUtil
} }
} }
public static PacketFDEncryptionRequest encryptRequest() public static PacketFDEncryptionRequest encryptRequest(boolean onlinemode)
{ {
String hash = ( BungeeCord.getInstance().config.isOnlineMode() ) ? Long.toString( random.nextLong(), 16 ) : "-"; String hash = ( onlinemode ) ? Long.toString( random.nextLong(), 16 ) : "-";
byte[] pubKey = keys.getPublic().getEncoded(); byte[] pubKey = keys.getPublic().getEncoded();
byte[] verify = new byte[ 4 ]; byte[] verify = new byte[ 4 ];
random.nextBytes( verify ); random.nextBytes( verify );

View File

@@ -1,5 +1,7 @@
package net.md_5.bungee; package net.md_5.bungee;
import io.netty.buffer.ByteBuf;
/** /**
* Class to rewrite integers within packets. * Class to rewrite integers within packets.
*/ */
@@ -102,6 +104,10 @@ public class EntityMap
{ {
1 1
}; };
entityIds[0x2C] = new int[]
{
1
};
entityIds[0x37] = new int[] entityIds[0x37] = new int[]
{ {
1 1
@@ -113,20 +119,20 @@ public class EntityMap
}; };
} }
public static void rewrite(byte[] packet, int oldId, int newId) public static void rewrite(ByteBuf packet, int oldId, int newId)
{ {
int packetId = packet[0] & 0xFF; int packetId = packet.getUnsignedByte( 0 );
if ( packetId == 0x1D ) if ( packetId == 0x1D )
{ // bulk entity { // bulk entity
for ( int pos = 2; pos < packet.length; pos += 4 ) for ( int pos = 2; pos < packet.readableBytes(); pos += 4 )
{ {
int readId = readInt( packet, pos ); int readId = packet.getInt( pos );
if ( readId == oldId ) if ( readId == oldId )
{ {
setInt( packet, pos, newId ); packet.setInt( pos, newId );
} else if ( readId == newId ) } else if ( readId == newId )
{ {
setInt( packet, pos, oldId ); packet.setInt( pos, oldId );
} }
} }
} else } else
@@ -136,41 +142,28 @@ public class EntityMap
{ {
for ( int pos : idArray ) for ( int pos : idArray )
{ {
int readId = readInt( packet, pos ); int readId = packet.getInt( pos );
if ( readId == oldId ) if ( readId == oldId )
{ {
setInt( packet, pos, newId ); packet.setInt( pos, newId );
} else if ( readId == newId ) } else if ( readId == newId )
{ {
setInt( packet, pos, oldId ); packet.setInt( pos, oldId );
} }
} }
} }
} }
if ( packetId == 0x17 ) if ( packetId == 0x17 )
{ {
int type = packet[5] & 0xFF; int type = packet.getUnsignedByte( 5 );
if ( type == 60 || type == 90 ) if ( type == 60 || type == 90 )
{ {
int index20 = readInt( packet, 20 ); int index20 = packet.getInt( 20 );
if ( packet.length > 24 && index20 == oldId ) if ( packet.readableBytes() > 24 && index20 == oldId )
{ {
setInt( packet, 20, newId ); packet.setInt( 20, newId );
} }
} }
} }
} }
private static void setInt(byte[] buf, int pos, int i)
{
buf[pos] = (byte) ( i >> 24 );
buf[pos + 1] = (byte) ( i >> 16 );
buf[pos + 2] = (byte) ( i >> 8 );
buf[pos + 3] = (byte) i;
}
private static int readInt(byte[] buf, int pos)
{
return ( ( ( buf[pos] & 0xFF ) << 24 ) | ( ( buf[pos + 1] & 0xFF ) << 16 ) | ( ( buf[pos + 2] & 0xFF ) << 8 ) | buf[pos + 3] & 0xFF );
}
} }

View File

@@ -42,6 +42,7 @@ public class ServerConnection implements Server
{ {
if ( !ch.isClosed() ) if ( !ch.isClosed() )
{ {
// TODO: Can we just use a future here?
unsafe().sendPacket( new PacketFFKick( reason ) ); unsafe().sendPacket( new PacketFFKick( reason ) );
ch.getHandle().eventLoop().schedule( new Runnable() ch.getHandle().eventLoop().schedule( new Runnable()
{ {

View File

@@ -1,9 +1,9 @@
package net.md_5.bungee; package net.md_5.bungee;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import java.io.DataInput;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.Objects; import java.util.Objects;
import java.util.Queue; import java.util.Queue;
@@ -29,6 +29,7 @@ import net.md_5.bungee.netty.PacketDecoder;
import net.md_5.bungee.netty.PacketHandler; import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.netty.PipelineUtils; import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.Forge; 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.DefinedPacket;
import net.md_5.bungee.protocol.packet.Packet1Login; import net.md_5.bungee.protocol.packet.Packet1Login;
import net.md_5.bungee.protocol.packet.Packet9Respawn; import net.md_5.bungee.protocol.packet.Packet9Respawn;
@@ -153,6 +154,10 @@ public class ServerConnector extends PacketHandler
(byte) user.getPendingConnection().getListener().getTabListSize() ); (byte) user.getPendingConnection().getListener().getTabListSize() );
} }
user.unsafe().sendPacket( modLogin ); 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 } else
{ {
user.getTabList().onServerChange(); user.getTabList().onServerChange();
@@ -220,7 +225,7 @@ public class ServerConnector extends PacketHandler
ch.write( new PacketFCEncryptionResponse( shared, token ) ); ch.write( new PacketFCEncryptionResponse( shared, token ) );
Cipher encrypt = EncryptionUtil.getCipher( Cipher.ENCRYPT_MODE, secretkey ); Cipher encrypt = EncryptionUtil.getCipher( Cipher.ENCRYPT_MODE, secretkey );
ch.getHandle().pipeline().addBefore( PipelineUtils.PACKET_DECODE_HANDLER, PipelineUtils.ENCRYPT_HANDLER, new CipherEncoder( encrypt ) ); ch.addBefore( PipelineUtils.PACKET_DECODE_HANDLER, PipelineUtils.ENCRYPT_HANDLER, new CipherEncoder( encrypt ) );
thisState = State.ENCRYPT_RESPONSE; thisState = State.ENCRYPT_RESPONSE;
} else } else
@@ -235,7 +240,7 @@ public class ServerConnector extends PacketHandler
Preconditions.checkState( thisState == State.ENCRYPT_RESPONSE, "Not expecting ENCRYPT_RESPONSE" ); Preconditions.checkState( thisState == State.ENCRYPT_RESPONSE, "Not expecting ENCRYPT_RESPONSE" );
Cipher decrypt = EncryptionUtil.getCipher( Cipher.DECRYPT_MODE, secretkey ); Cipher decrypt = EncryptionUtil.getCipher( Cipher.DECRYPT_MODE, secretkey );
ch.getHandle().pipeline().addBefore( PipelineUtils.PACKET_DECODE_HANDLER, PipelineUtils.DECRYPT_HANDLER, new CipherDecoder( decrypt ) ); ch.addBefore( PipelineUtils.PACKET_DECODE_HANDLER, PipelineUtils.DECRYPT_HANDLER, new CipherDecoder( decrypt ) );
ch.write( user.getPendingConnection().getForgeLogin() ); ch.write( user.getPendingConnection().getForgeLogin() );
@@ -251,14 +256,14 @@ public class ServerConnector extends PacketHandler
{ {
def = null; def = null;
} }
ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( user, kick.getMessage(), def ) ); ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( user, kick.getMessage(), def, ServerKickEvent.State.CONNECTING ) );
if ( event.isCancelled() && event.getCancelServer() != null ) if ( event.isCancelled() && event.getCancelServer() != null )
{ {
user.connect( event.getCancelServer() ); user.connect( event.getCancelServer() );
return; return;
} }
String message = bungee.getTranslation( "connect_kick" ) + target.getName() + ": " + kick.getMessage(); String message = bungee.getTranslation( "connect_kick" ) + target.getName() + ": " + event.getKickReason();
if ( user.getServer() == null ) if ( user.getServer() == null )
{ {
user.disconnect( message ); user.disconnect( message );
@@ -276,10 +281,9 @@ public class ServerConnector extends PacketHandler
throw new IllegalStateException( "May not connect to another BungeCord!" ); throw new IllegalStateException( "May not connect to another BungeCord!" );
} }
if ( pluginMessage.getTag().equals( "FML" ) && ( pluginMessage.getData()[0] & 0xFF ) == 0 ) DataInput in = pluginMessage.getStream();
if ( pluginMessage.getTag().equals( "FML" ) && in.readUnsignedByte() == 0 )
{ {
ByteArrayDataInput in = ByteStreams.newDataInput( pluginMessage.getData() );
in.readUnsignedByte();
int count = in.readInt(); int count = in.readInt();
for ( int i = 0; i < count; i++ ) for ( int i = 0; i < count; i++ )
{ {

View File

@@ -22,7 +22,6 @@ import lombok.Setter;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.config.TexturePackInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PermissionCheckEvent; import net.md_5.bungee.api.event.PermissionCheckEvent;
import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.event.ServerConnectEvent;
@@ -31,6 +30,7 @@ import net.md_5.bungee.api.tab.TabListHandler;
import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss; 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.netty.PipelineUtils;
import net.md_5.bungee.protocol.packet.DefinedPacket; import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.protocol.packet.Packet3Chat; import net.md_5.bungee.protocol.packet.Packet3Chat;
@@ -73,6 +73,9 @@ public final class UserConnection implements ProxiedPlayer
@Getter @Getter
@Setter @Setter
private int ping = 100; private int ping = 100;
@Getter
@Setter
private ServerInfo reconnectServer;
/*========================================================================*/ /*========================================================================*/
private final Collection<String> groups = new CaseInsensitiveSet(); private final Collection<String> groups = new CaseInsensitiveSet();
private final Collection<String> permissions = new CaseInsensitiveSet(); private final Collection<String> permissions = new CaseInsensitiveSet();
@@ -127,9 +130,9 @@ public final class UserConnection implements ProxiedPlayer
this.tabList = tabList; this.tabList = tabList;
} }
public void sendPacket(byte[] b) public void sendPacket(PacketWrapper packet)
{ {
ch.write( b ); ch.write( packet );
} }
@Deprecated @Deprecated
@@ -168,6 +171,8 @@ public final class UserConnection implements ProxiedPlayer
public void connect(ServerInfo info, final boolean retry) public void connect(ServerInfo info, final boolean retry)
{ {
Preconditions.checkNotNull( info, "info" );
ServerConnectEvent event = new ServerConnectEvent( this, info ); ServerConnectEvent event = new ServerConnectEvent( this, info );
if ( bungee.getPluginManager().callEvent( event ).isCancelled() ) if ( bungee.getPluginManager().callEvent( event ).isCancelled() )
{ {
@@ -178,12 +183,12 @@ public final class UserConnection implements ProxiedPlayer
if ( getServer() != null && Objects.equals( getServer().getInfo(), target ) ) if ( getServer() != null && Objects.equals( getServer().getInfo(), target ) )
{ {
sendMessage( ChatColor.RED + "Cannot connect to server you are already on!" ); sendMessage( bungee.getTranslation( "already_connected" ) );
return; return;
} }
if ( pendingConnects.contains( target ) ) if ( pendingConnects.contains( target ) )
{ {
sendMessage( ChatColor.RED + "Already connecting to this server!" ); sendMessage( bungee.getTranslation( "already_connecting" ) );
return; return;
} }
@@ -209,7 +214,7 @@ public final class UserConnection implements ProxiedPlayer
pendingConnects.remove( target ); pendingConnects.remove( target );
ServerInfo def = ProxyServer.getInstance().getServers().get( getPendingConnection().getListener().getFallbackServer() ); ServerInfo def = ProxyServer.getInstance().getServers().get( getPendingConnection().getListener().getFallbackServer() );
if ( retry & target != def && ( getServer() == null || def != getServer().getInfo() ) ) if ( retry && target != def && ( getServer() == null || def != getServer().getInfo() ) )
{ {
sendMessage( bungee.getTranslation( "fallback_lobby" ) ); sendMessage( bungee.getTranslation( "fallback_lobby" ) );
connect( def, false ); connect( def, false );
@@ -233,7 +238,7 @@ public final class UserConnection implements ProxiedPlayer
.option( ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000 ) // TODO: Configurable .option( ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000 ) // TODO: Configurable
.remoteAddress( target.getAddress() ); .remoteAddress( target.getAddress() );
// Windows is bugged, multi homed users will just have to live with random connecting IPs // Windows is bugged, multi homed users will just have to live with random connecting IPs
if ( !PlatformDependent.isWindows() ) if ( getPendingConnection().getListener().isSetLocalAddress() && !PlatformDependent.isWindows() )
{ {
b.localAddress( getPendingConnection().getListener().getHost().getHostString(), 0 ); b.localAddress( getPendingConnection().getListener().getHost().getHostString(), 0 );
} }
@@ -265,7 +270,9 @@ public final class UserConnection implements ProxiedPlayer
@Override @Override
public void sendMessage(String message) public void sendMessage(String message)
{ {
unsafe().sendPacket( new Packet3Chat( message ) ); // TODO: Fix this
String encoded = BungeeCord.getInstance().gson.toJson( message );
unsafe().sendPacket( new Packet3Chat( "{\"text\":" + encoded + "}" ) );
} }
@Override @Override
@@ -345,12 +352,6 @@ public final class UserConnection implements ProxiedPlayer
return name; return name;
} }
@Override
public void setTexturePack(TexturePackInfo pack)
{
unsafe().sendPacket( new PacketFAPluginMessage( "MC|TPack", ( pack.getUrl() + "\00" + pack.getSize() ).getBytes() ) );
}
@Override @Override
public Unsafe unsafe() public Unsafe unsafe()
{ {

View File

@@ -54,12 +54,12 @@ public class Util
+ ( ( trace.length > 0 ) ? " @ " + t.getStackTrace()[0].getClassName() + ":" + t.getStackTrace()[0].getLineNumber() : "" ); + ( ( trace.length > 0 ) ? " @ " + t.getStackTrace()[0].getClassName() + ":" + t.getStackTrace()[0].getLineNumber() : "" );
} }
public static String csv(Collection<?> objects) public static String csv(Iterable<?> objects)
{ {
return format( objects, ", " ); return format( objects, ", " );
} }
public static String format(Collection<?> objects, String separators) public static String format(Iterable<?> objects, String separators)
{ {
StringBuilder ret = new StringBuilder(); StringBuilder ret = new StringBuilder();
for ( Object o : objects ) for ( Object o : objects )

View File

@@ -41,7 +41,6 @@ public class CommandAlert extends Command
String message = builder.substring( 0, builder.length() - 1 ); String message = builder.substring( 0, builder.length() - 1 );
ProxyServer.getInstance().broadcast( message ); ProxyServer.getInstance().broadcast( message );
ProxyServer.getInstance().getConsole().sendMessage( message );
} }
} }
} }

View File

@@ -1,12 +1,12 @@
package net.md_5.bungee.command; package net.md_5.bungee.command;
import java.util.Collections;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
public class CommandFind extends Command public class CommandFind extends PlayerCommand
{ {
public CommandFind() public CommandFind()

View File

@@ -1,7 +1,6 @@
package net.md_5.bungee.command; package net.md_5.bungee.command;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import net.md_5.bungee.Util; import net.md_5.bungee.Util;
@@ -33,31 +32,16 @@ public class CommandList extends Command
continue; continue;
} }
Collection<ProxiedPlayer> serverPlayers = server.getPlayers();
StringBuilder message = new StringBuilder();
message.append( ChatColor.GREEN );
message.append( "[" );
message.append( server.getName() );
message.append( "] " );
message.append( ChatColor.YELLOW );
message.append( "(" );
message.append( serverPlayers.size() );
message.append( "): " );
message.append( ChatColor.RESET );
List<String> players = new ArrayList<>(); List<String> players = new ArrayList<>();
for ( ProxiedPlayer player : serverPlayers ) for ( ProxiedPlayer player : server.getPlayers() )
{ {
players.add( player.getDisplayName() ); players.add( player.getDisplayName() );
} }
Collections.sort( players, String.CASE_INSENSITIVE_ORDER ); Collections.sort( players, String.CASE_INSENSITIVE_ORDER );
message.append( Util.format( players, ChatColor.RESET + ", " ) ); sender.sendMessage( ProxyServer.getInstance().getTranslation( "command_list", server.getName(), server.getPlayers().size(), Util.format( players, ChatColor.RESET + ", " ) ) );
sender.sendMessage( message.toString() );
} }
sender.sendMessage( ProxyServer.getInstance().getTranslation( "total_players" ) + ProxyServer.getInstance().getOnlineCount() ); sender.sendMessage( ProxyServer.getInstance().getTranslation( "total_players", ProxyServer.getInstance().getOnlineCount() ) );
} }
} }

View File

@@ -2,6 +2,7 @@ package net.md_5.bungee.command;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
@@ -18,15 +19,12 @@ public class CommandPerms extends Command
@Override @Override
public void execute(CommandSender sender, String[] args) public void execute(CommandSender sender, String[] args)
{ {
StringBuilder groups = new StringBuilder();
Set<String> permissions = new HashSet<>(); Set<String> permissions = new HashSet<>();
for ( String group : sender.getGroups() ) for ( String group : sender.getGroups() )
{ {
groups.append( group );
groups.append( ", " );
permissions.addAll( ProxyServer.getInstance().getConfigurationAdapter().getPermissions( group ) ); permissions.addAll( ProxyServer.getInstance().getConfigurationAdapter().getPermissions( group ) );
} }
sender.sendMessage( ChatColor.GOLD + "You have the following groups: " + groups.substring( 0, groups.length() - 2 ) ); sender.sendMessage( ChatColor.GOLD + "You have the following groups: " + Util.csv( sender.getGroups() ) );
for ( String permission : permissions ) for ( String permission : permissions )
{ {

View File

@@ -1,16 +1,21 @@
package net.md_5.bungee.command; 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 java.util.Map;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command; 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. * Command to list and switch a player between available servers.
*/ */
public class CommandServer extends Command public class CommandServer extends Command implements TabExecutor
{ {
public CommandServer() public CommandServer()
@@ -60,4 +65,24 @@ public class CommandServer extends Command
} }
} }
} }
@Override
public Iterable<String> onTabComplete(final CommandSender sender, String[] args)
{
return ( args.length != 0 ) ? Collections.EMPTY_LIST : Iterables.transform( Iterables.filter( ProxyServer.getInstance().getServers().values(), new Predicate<ServerInfo>()
{
@Override
public boolean apply(ServerInfo input)
{
return input.canAccess( sender );
}
} ), new Function<ServerInfo, String>()
{
@Override
public String apply(ServerInfo input)
{
return input.getName();
}
} );
}
} }

View File

@@ -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<String> 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<ProxiedPlayer>()
{
@Override
public boolean apply(ProxiedPlayer player)
{
return player.getName().startsWith( lastArg );
}
} ), new Function<ProxiedPlayer, String>()
{
@Override
public String apply(ProxiedPlayer player)
{
return player.getName();
}
} );
}
}

View File

@@ -2,6 +2,7 @@ package net.md_5.bungee.config;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import gnu.trove.map.TMap; import gnu.trove.map.TMap;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@@ -10,10 +11,8 @@ import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ConfigurationAdapter; import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.tab.GlobalPing;
import net.md_5.bungee.tab.Global;
import net.md_5.bungee.tab.ServerUnique;
import net.md_5.bungee.util.CaseInsensitiveMap; import net.md_5.bungee.util.CaseInsensitiveMap;
import net.md_5.bungee.util.CaseInsensitiveSet;
/** /**
* Core configuration for the proxy. * Core configuration for the proxy.
@@ -43,6 +42,8 @@ public class Configuration
*/ */
private boolean onlineMode = true; private boolean onlineMode = true;
private int playerLimit = -1; private int playerLimit = -1;
private Collection<String> disabledCommands;
private int throttle = 4000;
public void load() public void load()
{ {
@@ -54,6 +55,9 @@ public class Configuration
uuid = adapter.getString( "stats", uuid ); uuid = adapter.getString( "stats", uuid );
onlineMode = adapter.getBoolean( "online_mode", onlineMode ); onlineMode = adapter.getBoolean( "online_mode", onlineMode );
playerLimit = adapter.getInt( "player_limit", playerLimit ); playerLimit = adapter.getInt( "player_limit", playerLimit );
throttle = adapter.getInt( "connection_throttle", throttle );
disabledCommands = new CaseInsensitiveSet( (Collection<String>) adapter.getList( "disabled_commands", Arrays.asList( "find" ) ) );
Preconditions.checkArgument( listeners != null && !listeners.isEmpty(), "No listeners defined." ); Preconditions.checkArgument( listeners != null && !listeners.isEmpty(), "No listeners defined." );
@@ -84,6 +88,7 @@ public class Configuration
for ( ListenerInfo listener : listeners ) for ( ListenerInfo listener : listeners )
{ {
Preconditions.checkArgument( servers.containsKey( listener.getDefaultServer() ), "Default server %s is not defined", listener.getDefaultServer() ); Preconditions.checkArgument( servers.containsKey( listener.getDefaultServer() ), "Default server %s is not defined", listener.getDefaultServer() );
Preconditions.checkArgument( servers.containsKey( listener.getFallbackServer() ), "Fallback server %s is not defined", listener.getFallbackServer() );
} }
} }
} }

View File

@@ -21,7 +21,6 @@ import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ConfigurationAdapter; import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo; 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.api.tab.TabListHandler;
import net.md_5.bungee.tab.Global; import net.md_5.bungee.tab.Global;
import net.md_5.bungee.tab.GlobalPing; import net.md_5.bungee.tab.GlobalPing;
@@ -171,9 +170,10 @@ public class YamlConfig implements ConfigurationAdapter
Map<String, Object> val = entry.getValue(); Map<String, Object> val = entry.getValue();
String name = entry.getKey(); String name = entry.getKey();
String addr = get( "address", "localhost:25565", val ); String addr = get( "address", "localhost:25565", val );
String motd = ChatColor.translateAlternateColorCodes( '&', get( "motd", "&1Just another BungeeCord - Forced Host", val ) );
boolean restricted = get( "restricted", false, val ); boolean restricted = get( "restricted", false, val );
InetSocketAddress address = Util.getAddr( addr ); InetSocketAddress address = Util.getAddr( addr );
ServerInfo info = ProxyServer.getInstance().constructServerInfo( name, address, restricted ); ServerInfo info = ProxyServer.getInstance().constructServerInfo( name, address, motd, restricted );
ret.put( name, info ); ret.put( name, info );
} }
@@ -195,7 +195,7 @@ public class YamlConfig implements ConfigurationAdapter
for ( Map<String, Object> val : base ) for ( Map<String, Object> val : base )
{ {
String motd = get( "motd", "Another Bungee server", val ); String motd = get( "motd", "&1Another Bungee server", val );
motd = ChatColor.translateAlternateColorCodes( '&', motd ); motd = ChatColor.translateAlternateColorCodes( '&', motd );
int maxPlayers = get( "max_players", 1, val ); int maxPlayers = get( "max_players", 1, val );
@@ -206,17 +206,19 @@ public class YamlConfig implements ConfigurationAdapter
int tabListSize = get( "tab_size", 60, val ); int tabListSize = get( "tab_size", 60, val );
InetSocketAddress address = Util.getAddr( host ); InetSocketAddress address = Util.getAddr( host );
Map<String, String> forced = new CaseInsensitiveMap<>( get( "forced_hosts", forcedDef, val ) ); Map<String, String> forced = new CaseInsensitiveMap<>( get( "forced_hosts", forcedDef, val ) );
String textureURL = get( "texture_url", null, val );
int textureSize = get( "texture_size", 16, val );
TexturePackInfo texture = ( textureURL == null ) ? null : new TexturePackInfo( textureURL, textureSize );
String tabListName = get( "tab_list", "GLOBAL_PING", val ); String tabListName = get( "tab_list", "GLOBAL_PING", val );
DefaultTabList value = DefaultTabList.valueOf( tabListName.toUpperCase() ); DefaultTabList value = DefaultTabList.valueOf( tabListName.toUpperCase() );
if ( value == null ) if ( value == null )
{ {
value = DefaultTabList.GLOBAL_PING; value = DefaultTabList.GLOBAL_PING;
} }
boolean setLocalAddress = get( "bind_local_address", true, val );
boolean pingPassthrough = get( "ping_passthrough", false, val );
ListenerInfo info = new ListenerInfo( address, motd, maxPlayers, tabListSize, defaultServer, fallbackServer, forceDefault, forced, texture, value.clazz ); boolean query = get( "query_enabled", false, val );
int queryPort = get( "query_port", 25577, val );
ListenerInfo info = new ListenerInfo( address, motd, maxPlayers, tabListSize, defaultServer, fallbackServer, forceDefault, forced, value.clazz, setLocalAddress, pingPassthrough, queryPort, query );
ret.add( info ); ret.add( info );
} }
@@ -233,6 +235,12 @@ public class YamlConfig implements ConfigurationAdapter
return ret; return ret;
} }
@Override
public Collection<?> getList(String path, Collection<?> def)
{
return get( path, def );
}
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Collection<String> getPermissions(String group) public Collection<String> getPermissions(String group)

View File

@@ -1,15 +1,14 @@
package net.md_5.bungee.connection; package net.md_5.bungee.connection;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import java.io.DataInput;
import java.util.Objects; import java.util.Objects;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.md_5.bungee.EntityMap; import net.md_5.bungee.EntityMap;
import net.md_5.bungee.ServerConnection; import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util; 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.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -22,6 +21,7 @@ import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.score.Team; import net.md_5.bungee.api.score.Team;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.PacketHandler; 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.Packet0KeepAlive;
import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem; import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem;
import net.md_5.bungee.protocol.packet.PacketCEScoreboardObjective; import net.md_5.bungee.protocol.packet.PacketCEScoreboardObjective;
@@ -31,8 +31,6 @@ import net.md_5.bungee.protocol.packet.PacketD1Team;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.protocol.packet.PacketFFKick; import net.md_5.bungee.protocol.packet.PacketFFKick;
;
@RequiredArgsConstructor @RequiredArgsConstructor
public class DownstreamBridge extends PacketHandler public class DownstreamBridge extends PacketHandler
{ {
@@ -47,8 +45,9 @@ public class DownstreamBridge extends PacketHandler
ServerInfo def = bungee.getServerInfo( con.getPendingConnection().getListener().getFallbackServer() ); ServerInfo def = bungee.getServerInfo( con.getPendingConnection().getListener().getFallbackServer() );
if ( server.getInfo() != def ) if ( server.getInfo() != def )
{ {
server.setObsolete( true );
con.connectNow( def ); con.connectNow( def );
con.sendMessage( ChatColor.RED + "The server you were previously on went down, you have been connected to the lobby" ); con.sendMessage( bungee.getTranslation( "server_went_down" ) );
} else } else
{ {
con.disconnect( Util.exception( t ) ); con.disconnect( Util.exception( t ) );
@@ -60,7 +59,10 @@ public class DownstreamBridge extends PacketHandler
{ {
// We lost connection to the server // We lost connection to the server
server.getInfo().removePlayer( con ); server.getInfo().removePlayer( con );
if ( bungee.getReconnectHandler() != null )
{
bungee.getReconnectHandler().setServer( con ); bungee.getReconnectHandler().setServer( con );
}
if ( !server.isObsolete() ) if ( !server.isObsolete() )
{ {
@@ -69,12 +71,12 @@ public class DownstreamBridge extends PacketHandler
} }
@Override @Override
public void handle(byte[] buf) throws Exception public void handle(PacketWrapper packet) throws Exception
{ {
if ( !server.isObsolete() ) if ( !server.isObsolete() )
{ {
EntityMap.rewrite( buf, con.getServerEntityId(), con.getClientEntityId() ); EntityMap.rewrite( packet.buf, con.getServerEntityId(), con.getClientEntityId() );
con.sendPacket( buf ); con.sendPacket( packet );
} }
} }
@@ -185,7 +187,7 @@ public class DownstreamBridge extends PacketHandler
@Override @Override
public void handle(PacketFAPluginMessage pluginMessage) throws Exception public void handle(PacketFAPluginMessage pluginMessage) throws Exception
{ {
ByteArrayDataInput in = ByteStreams.newDataInput( pluginMessage.getData() ); DataInput in = pluginMessage.getStream();
PluginMessageEvent event = new PluginMessageEvent( con.getServer(), con, pluginMessage.getTag(), pluginMessage.getData().clone() ); PluginMessageEvent event = new PluginMessageEvent( con.getServer(), con, pluginMessage.getTag(), pluginMessage.getData().clone() );
if ( bungee.getPluginManager().callEvent( event ).isCancelled() ) if ( bungee.getPluginManager().callEvent( event ).isCancelled() )
@@ -193,11 +195,6 @@ public class DownstreamBridge extends PacketHandler
throw new CancelSendSignal(); throw new CancelSendSignal();
} }
if ( pluginMessage.getTag().equals( "MC|TPack" ) && con.getPendingConnection().getListener().getTexturePack() != null )
{
throw new CancelSendSignal();
}
if ( pluginMessage.getTag().equals( "BungeeCord" ) ) if ( pluginMessage.getTag().equals( "BungeeCord" ) )
{ {
ByteArrayDataOutput out = ByteStreams.newDataOutput(); ByteArrayDataOutput out = ByteStreams.newDataOutput();
@@ -247,6 +244,18 @@ public class DownstreamBridge extends PacketHandler
con.connect( server ); con.connect( server );
} }
} }
if ( subChannel.equals( "ConnectOther" ) )
{
ProxiedPlayer player = bungee.getPlayer( in.readUTF() );
if ( player != null )
{
ServerInfo server = bungee.getServerInfo( in.readUTF() );
if ( server != null )
{
player.connect( server );
}
}
}
if ( subChannel.equals( "IP" ) ) if ( subChannel.equals( "IP" ) )
{ {
out.writeUTF( "IP" ); out.writeUTF( "IP" );
@@ -328,7 +337,7 @@ public class DownstreamBridge extends PacketHandler
{ {
def = null; def = null;
} }
ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( con, kick.getMessage(), def ) ); ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( con, kick.getMessage(), def, ServerKickEvent.State.CONNECTED ) );
if ( event.isCancelled() && event.getCancelServer() != null ) if ( event.isCancelled() && event.getCancelServer() != null )
{ {
con.connectNow( event.getCancelServer() ); con.connectNow( event.getCancelServer() );

View File

@@ -1,8 +1,7 @@
package net.md_5.bungee.connection; package net.md_5.bungee.connection;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.ning.http.client.AsyncCompletionHandler; import io.netty.util.concurrent.ScheduledFuture;
import com.ning.http.client.Response;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URLEncoder; import java.net.URLEncoder;
@@ -10,6 +9,7 @@ import java.security.GeneralSecurityException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@@ -31,6 +31,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ProxyPingEvent; import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.http.HttpClient;
import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.CipherDecoder; import net.md_5.bungee.netty.CipherDecoder;
@@ -39,6 +40,7 @@ import net.md_5.bungee.netty.PacketDecoder;
import net.md_5.bungee.netty.PacketHandler; import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.netty.PipelineUtils; import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.Forge; import net.md_5.bungee.protocol.Forge;
import net.md_5.bungee.protocol.MinecraftInput;
import net.md_5.bungee.protocol.Vanilla; import net.md_5.bungee.protocol.Vanilla;
import net.md_5.bungee.protocol.packet.DefinedPacket; import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.protocol.packet.Packet1Login; import net.md_5.bungee.protocol.packet.Packet1Login;
@@ -49,6 +51,8 @@ import net.md_5.bungee.protocol.packet.PacketFCEncryptionResponse;
import net.md_5.bungee.protocol.packet.PacketFDEncryptionRequest; import net.md_5.bungee.protocol.packet.PacketFDEncryptionRequest;
import net.md_5.bungee.protocol.packet.PacketFEPing; import net.md_5.bungee.protocol.packet.PacketFEPing;
import net.md_5.bungee.protocol.packet.PacketFFKick; import net.md_5.bungee.protocol.packet.PacketFFKick;
import net.md_5.bungee.api.AbstractReconnectHandler;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
@RequiredArgsConstructor @RequiredArgsConstructor
public class InitialHandler extends PacketHandler implements PendingConnection public class InitialHandler extends PacketHandler implements PendingConnection
@@ -77,6 +81,11 @@ public class InitialHandler extends PacketHandler implements PendingConnection
ch.write( packet ); ch.write( packet );
} }
}; };
@Getter
private boolean onlineMode = BungeeCord.getInstance().config.isOnlineMode();
private ScheduledFuture<?> pingFuture;
private InetSocketAddress vHost;
private byte version = -1;
private enum State private enum State
{ {
@@ -99,6 +108,22 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override @Override
public void handle(PacketFAPluginMessage pluginMessage) throws Exception public void handle(PacketFAPluginMessage pluginMessage) throws Exception
{ {
if ( pluginMessage.getTag().equals( "MC|PingHost" ) )
{
if ( pingFuture.cancel( false ) )
{
MinecraftInput in = pluginMessage.getMCStream();
version = in.readByte();
String connectHost = in.readString();
int connectPort = in.readInt();
this.vHost = new InetSocketAddress( connectHost, connectPort );
respondToPing();
}
return;
}
// TODO: Unregister? // TODO: Unregister?
if ( pluginMessage.getTag().equals( "REGISTER" ) ) if ( pluginMessage.getTag().equals( "REGISTER" ) )
{ {
@@ -109,21 +134,53 @@ public class InitialHandler extends PacketHandler implements PendingConnection
} }
} }
private void respondToPing()
{
ServerInfo forced = AbstractReconnectHandler.getForcedHost( this );
final String motd = ( forced != null ) ? forced.getMotd() : listener.getMotd();
Callback<ServerPing> pingBack = new Callback<ServerPing>()
{
@Override
public void done(ServerPing result, Throwable error)
{
if ( error != null )
{
result = new ServerPing( (byte) -1, "-1", "Error pinging remote server: " + Util.exception( error ), -1, -1 );
}
result = bungee.getPluginManager().callEvent( new ProxyPingEvent( InitialHandler.this, result ) ).getResponse();
String kickMessage = ChatColor.DARK_BLUE
+ "\00" + result.getProtocolVersion()
+ "\00" + result.getGameVersion()
+ "\00" + result.getMotd()
+ "\00" + result.getCurrentPlayers()
+ "\00" + result.getMaxPlayers();
BungeeCord.getInstance().getConnectionThrottle().unthrottle( getAddress().getAddress() );
disconnect( kickMessage );
}
};
if ( forced != null && listener.isPingPassthrough() )
{
forced.ping( pingBack );
} else
{
pingBack.done( new ServerPing( bungee.getProtocolVersion(), bungee.getGameVersion(), motd, bungee.getOnlineCount(), listener.getMaxPlayers() ), null );
}
}
@Override @Override
public void handle(PacketFEPing ping) throws Exception public void handle(PacketFEPing ping) throws Exception
{ {
ServerPing response = new ServerPing( bungee.getProtocolVersion(), bungee.getGameVersion(), pingFuture = ch.getHandle().eventLoop().schedule( new Runnable()
listener.getMotd(), bungee.getOnlineCount(), listener.getMaxPlayers() ); {
@Override
response = bungee.getPluginManager().callEvent( new ProxyPingEvent( this, response ) ).getResponse(); public void run()
{
String kickMessage = ChatColor.DARK_BLUE respondToPing();
+ "\00" + response.getProtocolVersion() }
+ "\00" + response.getGameVersion() }, 200, TimeUnit.MILLISECONDS );
+ "\00" + response.getMotd()
+ "\00" + response.getCurrentPlayers()
+ "\00" + response.getMaxPlayers();
disconnect( kickMessage );
} }
@Override @Override
@@ -141,14 +198,17 @@ public class InitialHandler extends PacketHandler implements PendingConnection
{ {
Preconditions.checkState( thisState == State.HANDSHAKE, "Not expecting HANDSHAKE" ); Preconditions.checkState( thisState == State.HANDSHAKE, "Not expecting HANDSHAKE" );
this.handshake = handshake; this.handshake = handshake;
this.vHost = new InetSocketAddress( handshake.getHost(), handshake.getPort() );
bungee.getLogger().log( Level.INFO, "{0} has connected", this ); bungee.getLogger().log( Level.INFO, "{0} has connected", this );
if ( handshake.getProcolVersion() > Vanilla.PROTOCOL_VERSION ) bungee.getPluginManager().callEvent( new PlayerHandshakeEvent( InitialHandler.this, handshake ) );
if ( handshake.getProtocolVersion() > Vanilla.PROTOCOL_VERSION )
{ {
disconnect( "Outdated server!" ); disconnect( bungee.getTranslation( "outdated_server" ) );
} else if ( handshake.getProcolVersion() < Vanilla.PROTOCOL_VERSION ) } else if ( handshake.getProtocolVersion() < Vanilla.PROTOCOL_VERSION )
{ {
disconnect( "Outdated client!" ); disconnect( bungee.getTranslation( "outdated_client" ) );
} }
if ( handshake.getUsername().length() > 16 ) if ( handshake.getUsername().length() > 16 )
@@ -165,7 +225,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
} }
// If offline mode and they are already on, don't allow connect // If offline mode and they are already on, don't allow connect
if ( !BungeeCord.getInstance().config.isOnlineMode() && bungee.getPlayer( handshake.getUsername() ) != null ) if ( !isOnlineMode() && bungee.getPlayer( handshake.getUsername() ) != null )
{ {
disconnect( bungee.getTranslation( "already_connected" ) ); disconnect( bungee.getTranslation( "already_connected" ) );
return; return;
@@ -173,7 +233,8 @@ public class InitialHandler extends PacketHandler implements PendingConnection
unsafe().sendPacket( PacketConstants.I_AM_BUNGEE ); unsafe().sendPacket( PacketConstants.I_AM_BUNGEE );
unsafe().sendPacket( PacketConstants.FORGE_MOD_REQUEST ); unsafe().sendPacket( PacketConstants.FORGE_MOD_REQUEST );
unsafe().sendPacket( request = EncryptionUtil.encryptRequest() );
unsafe().sendPacket( request = EncryptionUtil.encryptRequest( this.onlineMode ) );
thisState = State.ENCRYPT; thisState = State.ENCRYPT;
} }
@@ -184,9 +245,9 @@ public class InitialHandler extends PacketHandler implements PendingConnection
sharedKey = EncryptionUtil.getSecret( encryptResponse, request ); sharedKey = EncryptionUtil.getSecret( encryptResponse, request );
Cipher decrypt = EncryptionUtil.getCipher( Cipher.DECRYPT_MODE, sharedKey ); Cipher decrypt = EncryptionUtil.getCipher( Cipher.DECRYPT_MODE, sharedKey );
ch.getHandle().pipeline().addBefore( PipelineUtils.PACKET_DECODE_HANDLER, PipelineUtils.DECRYPT_HANDLER, new CipherDecoder( decrypt ) ); ch.addBefore( PipelineUtils.PACKET_DECODE_HANDLER, PipelineUtils.DECRYPT_HANDLER, new CipherDecoder( decrypt ) );
if ( BungeeCord.getInstance().config.isOnlineMode() ) if ( this.onlineMode )
{ {
String encName = URLEncoder.encode( InitialHandler.this.getName(), "UTF-8" ); String encName = URLEncoder.encode( InitialHandler.this.getName(), "UTF-8" );
@@ -201,35 +262,37 @@ public class InitialHandler extends PacketHandler implements PendingConnection
String encodedHash = URLEncoder.encode( new BigInteger( sha.digest() ).toString( 16 ), "UTF-8" ); String encodedHash = URLEncoder.encode( new BigInteger( sha.digest() ).toString( 16 ), "UTF-8" );
String authURL = "http://session.minecraft.net/game/checkserver.jsp?user=" + encName + "&serverId=" + encodedHash; String authURL = "http://session.minecraft.net/game/checkserver.jsp?user=" + encName + "&serverId=" + encodedHash;
bungee.getHttpClient().prepareGet( authURL ).execute( new AsyncCompletionHandler<Response>()
Callback<String> handler = new Callback<String>()
{ {
@Override @Override
public Response onCompleted(Response response) throws Exception public void done(String result, Throwable error)
{ {
if ( "YES".equals( response.getResponseBody() ) ) if ( error == null )
{
if ( "YES".equals( result ) )
{ {
finish(); finish();
} else } else
{ {
disconnect( "Not authenticated with Minecraft.net" ); disconnect( "Not authenticated with Minecraft.net" );
} }
return response; } else
}
@Override
public void onThrowable(Throwable t)
{ {
disconnect( bungee.getTranslation( "mojang_fail" ) ); disconnect( bungee.getTranslation( "mojang_fail" ) );
bungee.getLogger().log( Level.SEVERE, "Error authenticating " + getName() + " with minecraft.net", t ); bungee.getLogger().log( Level.SEVERE, "Error authenticating " + getName() + " with minecraft.net", error );
} }
} ); }
};
HttpClient.get( authURL, ch.getHandle().eventLoop(), handler );
} else } else
{ {
finish(); finish();
} }
} }
private void finish() throws GeneralSecurityException private void finish()
{ {
// Check for multiple connections // Check for multiple connections
ProxiedPlayer old = bungee.getPlayer( handshake.getUsername() ); ProxiedPlayer old = bungee.getPlayer( handshake.getUsername() );
@@ -257,17 +320,20 @@ public class InitialHandler extends PacketHandler implements PendingConnection
{ {
@Override @Override
public void run() public void run()
{
if ( ch.getHandle().isActive() )
{ {
unsafe().sendPacket( new PacketFCEncryptionResponse( new byte[ 0 ], new byte[ 0 ] ) ); unsafe().sendPacket( new PacketFCEncryptionResponse( new byte[ 0 ], new byte[ 0 ] ) );
try try
{ {
Cipher encrypt = EncryptionUtil.getCipher( Cipher.ENCRYPT_MODE, sharedKey ); Cipher encrypt = EncryptionUtil.getCipher( Cipher.ENCRYPT_MODE, sharedKey );
ch.getHandle().pipeline().addBefore( PipelineUtils.DECRYPT_HANDLER, PipelineUtils.ENCRYPT_HANDLER, new CipherEncoder( encrypt ) ); ch.addBefore( PipelineUtils.DECRYPT_HANDLER, PipelineUtils.ENCRYPT_HANDLER, new CipherEncoder( encrypt ) );
} catch ( GeneralSecurityException ex ) } catch ( GeneralSecurityException ex )
{ {
disconnect( "Cipher error: " + Util.exception( ex ) ); disconnect( "Cipher error: " + Util.exception( ex ) );
} }
} }
}
} ); } );
} }
}; };
@@ -281,14 +347,21 @@ public class InitialHandler extends PacketHandler implements PendingConnection
{ {
Preconditions.checkState( thisState == State.LOGIN, "Not expecting LOGIN" ); Preconditions.checkState( thisState == State.LOGIN, "Not expecting LOGIN" );
UserConnection userCon = new UserConnection( (BungeeCord) bungee, ch, getName(), this ); UserConnection userCon = new UserConnection( bungee, ch, getName(), this );
userCon.init(); userCon.init();
bungee.getPluginManager().callEvent( new PostLoginEvent( userCon ) ); bungee.getPluginManager().callEvent( new PostLoginEvent( userCon ) );
ch.getHandle().pipeline().get( HandlerBoss.class ).setHandler( new UpstreamBridge( bungee, userCon ) ); ch.getHandle().pipeline().get( HandlerBoss.class ).setHandler( new UpstreamBridge( bungee, userCon ) );
ServerInfo server = bungee.getReconnectHandler().getServer( userCon ); ServerInfo server;
if ( bungee.getReconnectHandler() != null )
{
server = bungee.getReconnectHandler().getServer( userCon );
} else
{
server = AbstractReconnectHandler.getForcedHost( this );
}
userCon.connect( server, true ); userCon.connect( server, true );
thisState = State.FINISHED; thisState = State.FINISHED;
@@ -314,13 +387,13 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override @Override
public byte getVersion() public byte getVersion()
{ {
return ( handshake == null ) ? -1 : handshake.getProcolVersion(); return ( handshake == null ) ? version : handshake.getProtocolVersion();
} }
@Override @Override
public InetSocketAddress getVirtualHost() public InetSocketAddress getVirtualHost()
{ {
return ( handshake == null ) ? null : new InetSocketAddress( handshake.getHost(), handshake.getPort() ); return vHost;
} }
@Override @Override
@@ -335,6 +408,12 @@ public class InitialHandler extends PacketHandler implements PendingConnection
return unsafe; return unsafe;
} }
public void setOnlineMode(boolean onlineMode)
{
Preconditions.checkState( thisState == State.HANDSHAKE, "Can only set online mode status whilst handshaking" );
this.onlineMode = onlineMode;
}
@Override @Override
public String toString() public String toString()
{ {

View File

@@ -1,5 +1,7 @@
package net.md_5.bungee.connection; package net.md_5.bungee.connection;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ServerPing; import net.md_5.bungee.api.ServerPing;
@@ -14,15 +16,15 @@ public class PingHandler extends PacketHandler
private final ServerInfo target; private final ServerInfo target;
private final Callback<ServerPing> callback; private final Callback<ServerPing> callback;
private static final byte[] pingBuf = new byte[]
{
(byte) 0xFE, (byte) 0x01
};
@Override @Override
public void connected(ChannelWrapper channel) throws Exception public void connected(ChannelWrapper channel) throws Exception
{ {
channel.write( pingBuf ); // TODO: Update this to 1.6.4 style!
channel.write( Unpooled.wrappedBuffer( new byte[]
{
(byte) 0xFE, (byte) 0x01
} ) );
} }
@Override @Override

View File

@@ -5,16 +5,19 @@ import net.md_5.bungee.EntityMap;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util; import net.md_5.bungee.Util;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.TexturePackInfo;
import net.md_5.bungee.api.event.ChatEvent; import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PluginMessageEvent; import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.PacketHandler; 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.Packet0KeepAlive;
import net.md_5.bungee.protocol.packet.Packet3Chat; 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.PacketCCSettings;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import java.util.ArrayList;
import java.util.List;
public class UpstreamBridge extends PacketHandler public class UpstreamBridge extends PacketHandler
{ {
@@ -30,12 +33,6 @@ public class UpstreamBridge extends PacketHandler
BungeeCord.getInstance().addConnection( con ); BungeeCord.getInstance().addConnection( con );
con.getTabList().onConnect(); con.getTabList().onConnect();
con.unsafe().sendPacket( BungeeCord.getInstance().registerChannels() ); con.unsafe().sendPacket( BungeeCord.getInstance().registerChannels() );
TexturePackInfo texture = con.getPendingConnection().getListener().getTexturePack();
if ( texture != null )
{
con.setTexturePack( texture );
}
} }
@Override @Override
@@ -60,12 +57,12 @@ public class UpstreamBridge extends PacketHandler
} }
@Override @Override
public void handle(byte[] buf) throws Exception public void handle(PacketWrapper packet) throws Exception
{ {
EntityMap.rewrite( buf, con.getClientEntityId(), con.getServerEntityId() ); EntityMap.rewrite( packet.buf, con.getClientEntityId(), con.getServerEntityId() );
if ( con.getServer() != null ) if ( con.getServer() != null )
{ {
con.getServer().getCh().write( buf ); con.getServer().getCh().write( packet );
} }
} }
@@ -84,14 +81,28 @@ public class UpstreamBridge extends PacketHandler
public void handle(Packet3Chat chat) throws Exception public void handle(Packet3Chat chat) throws Exception
{ {
ChatEvent chatEvent = new ChatEvent( con, con.getServer(), chat.getMessage() ); ChatEvent chatEvent = new ChatEvent( con, con.getServer(), chat.getMessage() );
if ( bungee.getPluginManager().callEvent( chatEvent ).isCancelled() ) 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(); throw new CancelSendSignal();
} }
if ( chatEvent.isCommand() )
@Override
public void handle(PacketCBTabComplete tabComplete) throws Exception
{ {
if ( bungee.getPluginManager().dispatchCommand( con, chat.getMessage().substring( 1 ) ) ) if ( tabComplete.getCursor().startsWith( "/" ) )
{ {
List<String> 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(); throw new CancelSendSignal();
} }
} }
@@ -111,7 +122,7 @@ public class UpstreamBridge extends PacketHandler
throw new CancelSendSignal(); throw new CancelSendSignal();
} }
// Hack around Forge race conditions // Hack around Forge race conditions
if ( pluginMessage.getTag().equals( "FML" ) && ( pluginMessage.getData()[0] & 0xFF ) == 1 ) if ( pluginMessage.getTag().equals( "FML" ) && pluginMessage.getStream().readUnsignedByte() == 1 )
{ {
throw new CancelSendSignal(); throw new CancelSendSignal();
} }

View File

@@ -0,0 +1,77 @@
package net.md_5.bungee.http;
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.ChannelOption;
import io.netty.channel.EventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import java.net.URI;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import net.md_5.bungee.api.Callback;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class HttpClient
{
public static int TIMEOUT = 5000;
public static void get(String url, EventLoop eventLoop, final Callback<String> callback)
{
Preconditions.checkNotNull( url, "url" );
Preconditions.checkNotNull( eventLoop, "eventLoop" );
Preconditions.checkNotNull( callback, "callBack" );
final URI uri = URI.create( url );
Preconditions.checkNotNull( uri.getScheme(), "scheme" );
Preconditions.checkNotNull( uri.getHost(), "host" );
boolean ssl = uri.getScheme().equals( "https" );
int port = uri.getPort();
if ( port == -1 )
{
switch ( uri.getScheme() )
{
case "http":
port = 80;
break;
case "https":
port = 443;
break;
default:
throw new IllegalArgumentException( "Unknown scheme " + uri.getScheme() );
}
}
ChannelFutureListener future = new ChannelFutureListener()
{
@Override
public void operationComplete(ChannelFuture future) throws Exception
{
if ( future.isSuccess() )
{
String path = uri.getRawPath() + ( ( uri.getRawQuery() == null ) ? "" : "?" + uri.getRawQuery() );
HttpRequest request = new DefaultHttpRequest( HttpVersion.HTTP_1_1, HttpMethod.GET, path );
request.headers().set( HttpHeaders.Names.HOST, uri.getHost() );
future.channel().writeAndFlush( request );
} else
{
callback.done( null, future.cause() );
}
}
};
new Bootstrap().channel( NioSocketChannel.class ).group( eventLoop ).handler( new HttpInitializer( callback, ssl ) ).
option( ChannelOption.CONNECT_TIMEOUT_MILLIS, TIMEOUT ).remoteAddress( uri.getHost(), port ).connect().addListener( future );
}
}

View File

@@ -0,0 +1,61 @@
package net.md_5.bungee.http;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.LastHttpContent;
import java.nio.charset.Charset;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.Callback;
@RequiredArgsConstructor
public class HttpHandler extends SimpleChannelInboundHandler<HttpObject>
{
private final Callback<String> callback;
private final StringBuilder buffer = new StringBuilder();
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
try
{
callback.done( null, cause );
} finally
{
ctx.channel().close();
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception
{
if ( msg instanceof HttpResponse )
{
HttpResponse response = (HttpResponse) msg;
if ( response.getStatus().code() != 200 )
{
throw new IllegalStateException( "Expected HTTP response 200 OK, got " + response.getStatus() );
}
}
if ( msg instanceof HttpContent )
{
HttpContent content = (HttpContent) msg;
buffer.append( content.content().toString( Charset.forName( "UTF-8" ) ) );
if ( msg instanceof LastHttpContent )
{
try
{
callback.done( buffer.toString(), null );
} finally
{
ctx.channel().close();
}
}
}
}
}

View File

@@ -0,0 +1,42 @@
package net.md_5.bungee.http;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.Callback;
@RequiredArgsConstructor
public class HttpInitializer extends ChannelInitializer<Channel>
{
private final Callback<String> callback;
private final boolean ssl;
@Override
protected void initChannel(Channel ch) throws Exception
{
ch.pipeline().addLast( "timeout", new ReadTimeoutHandler( HttpClient.TIMEOUT, TimeUnit.MILLISECONDS ) );
if ( ssl )
{
SSLContext context = SSLContext.getInstance( "TLS" );
context.init( null, new TrustManager[]
{
TrustingX509Manager.getInstance()
}, null );
SSLEngine engine = context.createSSLEngine();
engine.setUseClientMode( true );
ch.pipeline().addLast( "ssl", new SslHandler( engine ) );
}
ch.pipeline().addLast( "http", new HttpClientCodec() );
ch.pipeline().addLast( "handler", new HttpHandler( callback ) );
}
}

View File

@@ -0,0 +1,32 @@
package net.md_5.bungee.http;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TrustingX509Manager implements X509TrustManager
{
@Getter
private static final X509TrustManager instance = new TrustingX509Manager();
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return new X509Certificate[ 0 ];
}
}

View File

@@ -12,7 +12,7 @@ public class LogDispatcher extends Thread
public LogDispatcher(BungeeLogger logger) public LogDispatcher(BungeeLogger logger)
{ {
super( "BungeeCord Logger Thread - " + logger ); super( "BungeeCord Logger Thread" );
this.logger = logger; this.logger = logger;
} }

View File

@@ -1,6 +1,8 @@
package net.md_5.bungee.netty; package net.md_5.bungee.netty;
import com.google.common.base.Preconditions;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import lombok.Getter; import lombok.Getter;
@@ -19,9 +21,17 @@ public class ChannelWrapper
public synchronized void write(Object packet) public synchronized void write(Object packet)
{ {
if ( !closed ) if ( !closed )
{
if ( packet instanceof PacketWrapper )
{
( (PacketWrapper) packet ).setReleased( true );
ch.write( ( (PacketWrapper) packet ).buf );
} else
{ {
ch.write( packet ); ch.write( packet );
} }
ch.flush();
}
} }
public synchronized void close() public synchronized void close()
@@ -29,10 +39,18 @@ public class ChannelWrapper
if ( !closed ) if ( !closed )
{ {
closed = true; closed = true;
ch.flush();
ch.close(); 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() public Channel getHandle()
{ {
return ch; return ch;

View File

@@ -2,8 +2,8 @@ package net.md_5.bungee.netty;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.MessageList;
import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.handler.codec.MessageToMessageDecoder;
import java.util.List;
import javax.crypto.Cipher; import javax.crypto.Cipher;
public class CipherDecoder extends MessageToMessageDecoder<ByteBuf> public class CipherDecoder extends MessageToMessageDecoder<ByteBuf>
@@ -17,7 +17,7 @@ public class CipherDecoder extends MessageToMessageDecoder<ByteBuf>
} }
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, MessageList<Object> out) throws Exception protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception
{ {
out.add( cipher.cipher( ctx, msg ) ); out.add( cipher.cipher( ctx, msg ) );
} }

View File

@@ -3,7 +3,6 @@ package net.md_5.bungee.netty;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.MessageList;
import io.netty.handler.timeout.ReadTimeoutException; import io.netty.handler.timeout.ReadTimeoutException;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
@@ -11,6 +10,7 @@ import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.connection.CancelSendSignal; import net.md_5.bungee.connection.CancelSendSignal;
import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.PingHandler; 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 * This class is a primitive wrapper for {@link PacketHandler} instances tied to
@@ -59,30 +59,31 @@ public class HandlerBoss extends ChannelInboundHandlerAdapter
} }
@Override @Override
public void messageReceived(ChannelHandlerContext ctx, MessageList<Object> msgs) throws Exception public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
{ {
for ( Object msg : msgs ) if ( handler != null )
{
if ( handler != null && ctx.channel().isActive() )
{
if ( msg instanceof PacketWrapper )
{ {
PacketWrapper packet = (PacketWrapper) msg;
boolean sendPacket = true; boolean sendPacket = true;
try try
{ {
( (PacketWrapper) msg ).packet.handle( handler ); if ( packet.packet != null )
{
try
{
packet.packet.handle( handler );
} catch ( CancelSendSignal ex ) } catch ( CancelSendSignal ex )
{ {
sendPacket = false; sendPacket = false;
} }
}
if ( sendPacket ) if ( sendPacket )
{ {
handler.handle( ( (PacketWrapper) msg ).buf ); handler.handle( packet );
} }
} else } finally
{ {
handler.handle( (byte[]) msg ); packet.trySingleRelease();
}
} }
} }
} }
@@ -95,6 +96,9 @@ public class HandlerBoss extends ChannelInboundHandlerAdapter
if ( cause instanceof ReadTimeoutException ) if ( cause instanceof ReadTimeoutException )
{ {
ProxyServer.getInstance().getLogger().log( Level.WARNING, handler + " - read timed out" ); 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 ) } else if ( cause instanceof IOException )
{ {
ProxyServer.getInstance().getLogger().log( Level.WARNING, handler + " - IOException: " + cause.getMessage() ); ProxyServer.getInstance().getLogger().log( Level.WARNING, handler + " - IOException: " + cause.getMessage() );

View File

@@ -2,8 +2,8 @@ package net.md_5.bungee.netty;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.MessageList;
import io.netty.handler.codec.ReplayingDecoder; import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -28,7 +28,7 @@ public class PacketDecoder extends ReplayingDecoder<Void>
private Protocol protocol; private Protocol protocol;
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, MessageList<Object> out) throws Exception protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception
{ {
// While we have enough data // While we have enough data
while ( true ) while ( true )
@@ -40,24 +40,11 @@ public class PacketDecoder extends ReplayingDecoder<Void>
// If we got this far, it means we have formed a packet, so lets grab the end index // If we got this far, it means we have formed a packet, so lets grab the end index
int endIndex = in.readerIndex(); int endIndex = in.readerIndex();
// Allocate a buffer big enough for all bytes we have read // Allocate a buffer big enough for all bytes we have read
byte[] buf = new byte[ endIndex - startIndex ]; ByteBuf buf = in.copy( startIndex, endIndex - startIndex );
// Go back to start index
in.readerIndex( startIndex );
// Drain all the bytes into our buffer
in.readBytes( buf, 0, buf.length );
// Jump back to the end of this packet
in.readerIndex( endIndex );
// Checkpoint our state incase we don't have enough data for another packet // Checkpoint our state incase we don't have enough data for another packet
checkpoint(); checkpoint();
// Store our decoded message // Store our decoded message
if ( packet != null )
{
out.add( new PacketWrapper( packet, buf ) ); out.add( new PacketWrapper( packet, buf ) );
} else
{
out.add( buf );
}
} }
} }
} }

View File

@@ -10,7 +10,7 @@ public abstract class PacketHandler extends net.md_5.bungee.protocol.packet.Abst
{ {
} }
public void handle(byte[] buf) throws Exception public void handle(PacketWrapper packet) throws Exception
{ {
} }

View File

@@ -1,16 +1,25 @@
package net.md_5.bungee.netty; package net.md_5.bungee.netty;
import io.netty.buffer.ByteBuf;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import net.md_5.bungee.protocol.packet.DefinedPacket; import net.md_5.bungee.protocol.packet.DefinedPacket;
@RequiredArgsConstructor
public class PacketWrapper public class PacketWrapper
{ {
DefinedPacket packet; public final DefinedPacket packet;
byte[] buf; public final ByteBuf buf;
@Setter
private boolean released;
public PacketWrapper(DefinedPacket packet, byte[] buf) public void trySingleRelease()
{ {
this.packet = packet; if ( !released )
this.buf = buf; {
buf.release();
released = true;
}
} }
} }

View File

@@ -4,9 +4,9 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelException; import io.netty.channel.ChannelException;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption; import io.netty.channel.ChannelOption;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.BungeeServerInfo; import net.md_5.bungee.BungeeServerInfo;
@@ -28,6 +28,13 @@ public class PipelineUtils
@Override @Override
protected void initChannel(Channel ch) throws Exception protected void initChannel(Channel ch) throws Exception
{ {
if ( BungeeCord.getInstance().getConnectionThrottle().throttle( ( (InetSocketAddress) ch.remoteAddress() ).getAddress() ) )
{
// TODO: Better throttle - we can't throttle this way if we want to maintain 1.7 compat!
// ch.close();
// return;
}
BASE.initChannel( ch ); BASE.initChannel( ch );
ch.pipeline().get( HandlerBoss.class ).setHandler( new InitialHandler( ProxyServer.getInstance(), ch.attr( LISTENER ).get() ) ); ch.pipeline().get( HandlerBoss.class ).setHandler( new InitialHandler( ProxyServer.getInstance(), ch.attr( LISTENER ).get() ) );
} }
@@ -43,11 +50,9 @@ public class PipelineUtils
}; };
public static final Base BASE = new Base(); public static final Base BASE = new Base();
private static final DefinedPacketEncoder packetEncoder = new DefinedPacketEncoder(); private static final DefinedPacketEncoder packetEncoder = new DefinedPacketEncoder();
private static final ByteArrayEncoder arrayEncoder = new ByteArrayEncoder();
public static String TIMEOUT_HANDLER = "timeout"; public static String TIMEOUT_HANDLER = "timeout";
public static String PACKET_DECODE_HANDLER = "packet-decoder"; public static String PACKET_DECODE_HANDLER = "packet-decoder";
public static String PACKET_ENCODE_HANDLER = "packet-encoder"; public static String PACKET_ENCODE_HANDLER = "packet-encoder";
public static String ARRAY_ENCODE_HANDLER = "array-encoder";
public static String BOSS_HANDLER = "inbound-boss"; public static String BOSS_HANDLER = "inbound-boss";
public static String ENCRYPT_HANDLER = "encrypt"; public static String ENCRYPT_HANDLER = "encrypt";
public static String DECRYPT_HANDLER = "decrypt"; public static String DECRYPT_HANDLER = "decrypt";
@@ -69,7 +74,6 @@ public class PipelineUtils
ch.pipeline().addLast( TIMEOUT_HANDLER, new ReadTimeoutHandler( BungeeCord.getInstance().config.getTimeout(), TimeUnit.MILLISECONDS ) ); 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_DECODE_HANDLER, new PacketDecoder( Vanilla.getInstance() ) );
ch.pipeline().addLast( PACKET_ENCODE_HANDLER, packetEncoder ); ch.pipeline().addLast( PACKET_ENCODE_HANDLER, packetEncoder );
ch.pipeline().addLast( ARRAY_ENCODE_HANDLER, arrayEncoder );
ch.pipeline().addLast( BOSS_HANDLER, new HandlerBoss() ); ch.pipeline().addLast( BOSS_HANDLER, new HandlerBoss() );
} }
}; };

View File

@@ -1,36 +0,0 @@
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(ProxiedPlayer player)
{
ListenerInfo listener = player.getPendingConnection().getListener();
String name;
String forced = listener.getForcedHosts().get( player.getPendingConnection().getVirtualHost().getHostString() );
if ( forced == null && listener.isForceDefault() )
{
forced = listener.getDefaultServer();
}
String server = ( forced == null ) ? getStoredServer( player ) : forced;
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, "Default server not defined" );
return info;
}
protected abstract String getStoredServer(ProxiedPlayer player);
}

View File

@@ -1,94 +0,0 @@
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" );
connection = DriverManager.getConnection( "jdbc:sqlite:bungee.sqlite" );
try ( PreparedStatement ps = 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(ProxiedPlayer player)
{
String server = null;
try ( PreparedStatement ps = connection.prepareStatement( "SELECT server FROM players WHERE username = ?" ) )
{
ps.setString( 1, player.getName() );
try ( ResultSet rs = ps.executeQuery() )
{
if ( rs.next() )
{
server = rs.getString( 1 );
} else
{
try ( PreparedStatement playerUpdate = 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(ProxiedPlayer player)
{
try ( PreparedStatement ps = 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
{
connection.close();
} catch ( SQLException ex )
{
ProxyServer.getInstance().getLogger().log( Level.WARNING, "Error closing SQLite connection", ex );
}
}
}

View File

@@ -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<String, String> 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<String, String> 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()
{
}
}

View File

@@ -4,14 +4,16 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps; import com.google.common.collect.Multimaps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import gnu.trove.TCollections; import gnu.trove.TCollections;
import gnu.trove.map.TIntObjectMap; import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap; import gnu.trove.map.hash.TIntObjectHashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.scheduler.ScheduledTask; import net.md_5.bungee.api.scheduler.ScheduledTask;
import net.md_5.bungee.api.scheduler.TaskScheduler; import net.md_5.bungee.api.scheduler.TaskScheduler;
@@ -19,16 +21,22 @@ import net.md_5.bungee.api.scheduler.TaskScheduler;
public class BungeeScheduler implements 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 AtomicInteger taskCounter = new AtomicInteger();
private final TIntObjectMap<BungeeTask> tasks = TCollections.synchronizedMap( new TIntObjectHashMap<BungeeTask>() ); private final TIntObjectMap<BungeeTask> tasks = TCollections.synchronizedMap( new TIntObjectHashMap<BungeeTask>() );
private final Multimap<Plugin, BungeeTask> tasksByPlugin = Multimaps.synchronizedMultimap( HashMultimap.<Plugin, BungeeTask>create() ); private final Multimap<Plugin, BungeeTask> tasksByPlugin = Multimaps.synchronizedMultimap( HashMultimap.<Plugin, BungeeTask>create() );
public void shutdown()
{
s.shutdown();
}
@Override @Override
public void cancel(int id) public void cancel(int id)
{ {
BungeeTask task = tasks.remove( id ); BungeeTask task = tasks.remove( id );
task.cancel();
tasksByPlugin.values().remove( task ); tasksByPlugin.values().remove( task );
task.getFuture().cancel( false );
} }
@Override @Override
@@ -61,21 +69,17 @@ public class BungeeScheduler implements TaskScheduler
@Override @Override
public ScheduledTask schedule(Plugin owner, Runnable task, long delay, TimeUnit unit) public ScheduledTask schedule(Plugin owner, Runnable task, long delay, TimeUnit unit)
{ {
return prepare( owner, task ).setFuture( BungeeCord.getInstance().executors.schedule( task, delay, unit ) ); return schedule( owner, task, delay, 0, unit );
} }
@Override @Override
public ScheduledTask schedule(Plugin owner, Runnable task, long delay, long period, TimeUnit unit) public ScheduledTask schedule(Plugin owner, Runnable task, long delay, long period, TimeUnit unit)
{
return prepare( owner, task ).setFuture( BungeeCord.getInstance().executors.scheduleWithFixedDelay( task, delay, period, unit ) );
}
private BungeeTask prepare(Plugin owner, Runnable task)
{ {
Preconditions.checkNotNull( owner, "owner" ); Preconditions.checkNotNull( owner, "owner" );
Preconditions.checkNotNull( task, "task" ); Preconditions.checkNotNull( task, "task" );
BungeeTask prepared = new BungeeTask( taskCounter.getAndIncrement(), owner, task ); BungeeTask prepared = new BungeeTask( this, taskCounter.getAndIncrement(), owner, task, delay, period, unit );
tasks.put( prepared.getId(), prepared ); tasks.put( prepared.getId(), prepared );
s.execute( prepared );
return prepared; return prepared;
} }
} }

View File

@@ -1,32 +1,81 @@
package net.md_5.bungee.scheduler; package net.md_5.bungee.scheduler;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import lombok.AccessLevel; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import lombok.Data; import lombok.Data;
import lombok.Setter; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.scheduler.ScheduledTask; import net.md_5.bungee.api.scheduler.ScheduledTask;
@Data @Data
public class BungeeTask implements ScheduledTask public class BungeeTask implements Runnable, ScheduledTask
{ {
private final BungeeScheduler sched;
private final int id; private final int id;
private final Plugin owner; private final Plugin owner;
private final Runnable task; private final Runnable task;
@Setter(AccessLevel.NONE) //
private ScheduledFuture<?> future; 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 @Override
public long getDelay(TimeUnit unit) public void cancel()
{ {
return future.getDelay( unit ); running.set( false );
} }
BungeeTask setFuture(ScheduledFuture<?> future) @Override
public void run()
{ {
this.future = future; if ( delay > 0 )
return this; {
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 );
} }
} }

View File

@@ -1,28 +0,0 @@
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(ThreadFactory threadFactory)
{
super( Integer.MAX_VALUE, threadFactory );
setKeepAliveTime( 5, TimeUnit.MINUTES );
allowCoreThreadTimeOut( true );
}
@Override
protected void afterExecute(Runnable r, Throwable t)
{
super.afterExecute( r, t );
if ( t != null )
{
ProxyServer.getInstance().getLogger().log( Level.SEVERE, "Task caused exception whilst running", t );
}
}
}

View File

@@ -46,7 +46,7 @@ public class Custom extends TabListAdapter implements CustomTabList
if ( text != null ) if ( text != null )
{ {
Preconditions.checkArgument( text.length() <= MAX_LEN, "text must be <= %s chars", MAX_LEN ); 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" ); Preconditions.checkArgument( !ChatColor.stripColor( text ).isEmpty(), "Text cannot consist entirely of colour codes" );
text = attempt( text ); text = attempt( text );
sentStuff.add( text ); sentStuff.add( text );

View File

@@ -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}

View File

@@ -1,16 +0,0 @@
alert: \u00a78[\u00a74Alert\u00a78]\u00a7r
already_connected: You are already connected to the server
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
proxy_full: Server is full
restart: [Proxy] Proxy restarting.
server_kick: [Kicked]
server_list: \u00a76You may connect to the following servers at this time:
total_players: Total players online:

View File

@@ -0,0 +1,32 @@
package net.md_5.bungee;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.junit.Assert;
import org.junit.Test;
public class ThrottleTest
{
@Test
public void testThrottle() throws InterruptedException, UnknownHostException
{
ConnectionThrottle throttle = new ConnectionThrottle( 5 );
InetAddress address;
try {
address = InetAddress.getLocalHost();
} catch (UnknownHostException ex) {
address = InetAddress.getByName( null );
}
Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) );
Assert.assertTrue( "Address should be throttled", throttle.throttle( address ) );
throttle.unthrottle( address );
Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) );
Thread.sleep( 15 );
Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) );
}
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-shared-configuration>
<!--
This file contains additional configuration written by modules in the NetBeans IDE.
The configuration is intended to be shared among all the users of project and
therefore it is assumed to be part of version control checkout.
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
-->
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
<!--
Properties that influence various parts of the IDE, especially code formatting and the like.
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
That way multiple projects can share the same settings (useful for formatting rules for example).
Any value defined here will override the pom.xml file value but is only applicable to the current project.
-->
<org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>
</properties>
</project-shared-configuration>

35
query/pom.xml Normal file
View File

@@ -0,0 +1,35 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.6.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-query</artifactId>
<version>1.6.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Query</name>
<description>Minecraft query implementation based on the BungeeCord API.</description>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${netty.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,139 @@
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 lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@RequiredArgsConstructor
public class QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
{
private final ProxyServer bungee;
private final ListenerInfo listener;
/*========================================================================*/
private final Random random = new Random();
private final Map<Integer, Long> 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 channelRead0(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<String, String> 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<String, String> 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.writeAndFlush( response );
}
}

View File

@@ -0,0 +1,28 @@
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 lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ListenerInfo;
@RequiredArgsConstructor
public class RemoteQuery
{
private final ProxyServer bungee;
private final ListenerInfo 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 );
}
}