212 Commits

Author SHA1 Message Date
md_5
09d04c34cb Increase thread pool timeout to try and reduce churn 2013-04-30 11:32:29 +10:00
md_5
3132d2c7cf Increment intent counter 2013-04-29 18:53:16 +10:00
md_5
3682e8ba3a Catch throwable not exception for enabling plugins 2013-04-28 17:45:25 +10:00
md_5
82d5954f2f Revert "Load plugins in alphabetical-ish order."
This reverts commit 384228b751.
2013-04-28 17:44:23 +10:00
Steve Anton
384228b751 Load plugins in alphabetical-ish order. 2013-04-28 17:35:27 +10:00
md_5
50f8bd2eb0 Finish encapsulating all userconnection fields 2013-04-28 11:36:36 +10:00
md_5
ff32764f9a Encapsulate ping fields 2013-04-28 11:19:19 +10:00
md_5
a1f928b210 Don't expose the channel 2013-04-28 10:45:20 +10:00
md_5
5bf616dc4d Forgot that mutex 2013-04-28 10:41:03 +10:00
md_5
0d7759f50b Eeek, name should be constant! 2013-04-28 10:39:41 +10:00
md_5
824bdc5491 Refactor permissions to be lock free, and clean up duplicate references 2013-04-28 10:37:18 +10:00
md_5
ebff48ff86 Remove remnants of forge in initial handler 2013-04-28 10:27:13 +10:00
md_5
6efba44e5a Cleanup userconnection constructor 2013-04-28 10:26:26 +10:00
md_5
9f8ad518e8 Don't use trove sets 2013-04-28 10:21:15 +10:00
md_5
885a96c0c6 Update lombok/asynchttp/guava versions 2013-04-28 10:12:37 +10:00
md_5
5d0ff24f70 Update async http 2013-04-28 10:11:11 +10:00
md_5
ca5db43f70 Don't spew error about long usernames, just disconnect 2013-04-28 10:07:23 +10:00
md_5
49a22f188f Alert when we cannot bind 2013-04-28 09:42:38 +10:00
md_5
ce7c095243 Make ReusableChannelPromise less hacky 2013-04-28 08:53:14 +10:00
md_5
cbb08ec58b Duh, of course connect isn't called, because we are already connected! 2013-04-27 22:52:02 +10:00
md_5
349949d154 @normanmaurer, @trustin in relation to netty/1317 can we consider this a motion to allow channel.write(Object,null), I think it is a very good idea and cannot see it breaking any existing functionality at all. 2013-04-27 22:24:57 +10:00
md_5
92e7faa346 Stab at closing #285, /send current issues 2013-04-27 18:34:43 +10:00
md_5
6b21fdaaea Refactor packet writes into their own channel handler class. 2013-04-27 18:29:12 +10:00
md_5
fa9dd7e27f Synchronize yaml save to close #286, crash on end 2013-04-27 18:17:40 +10:00
md_5
f44cf6c8e9 Move some bootstrapping to the connected method, #267 2013-04-27 12:27:40 +10:00
md_5
6bf9df31f5 Fix async/login event 2013-04-27 12:25:03 +10:00
md_5
c08764990d Bigger logs, with rotation! 2013-04-26 20:23:16 +10:00
md_5
a82e6f3eea Use new servers 2013-04-26 17:48:32 +10:00
md_5
26cee397e6 Only alow config reloads to ADD servers. 2013-04-26 17:31:44 +10:00
md_5
dc6835c21b Add reason when default server is not defined 2013-04-26 17:23:04 +10:00
md_5
be30c8b89a Rename tab list classes 2013-04-26 17:20:03 +10:00
md_5
70e10c382e Properly interface ServerInfo class. 2013-04-26 17:13:00 +10:00
md_5
93ea108acb Complete issue #190 - unthread the login event for maximum efficiency. 2013-04-26 17:00:09 +10:00
md_5
a63739277b Remove depreceated register methods. 2013-04-26 16:49:37 +10:00
md-5
112d543c2a bump date 2013-04-26 11:50:20 +10:00
Robin Lambertz
7338e20e98 Change jarfile property in Plugin to file 2013-04-26 07:35:08 +10:00
Robin Lambertz
18a5534499 Add jarfile property to Plugin 2013-04-26 07:34:56 +10:00
md_5
e540626a28 Rewrite only arrows and rods. Closes #270 2013-04-20 09:46:23 +10:00
md_5
5e2bcc2907 Use custom AnnotatedHandlerFinder and pretend all events are thread safe 2013-04-15 09:15:20 +10:00
md_5
a38b3ce9f2 Try removing connections via string key and direct map access. Closes issue #267 2013-04-14 18:51:33 +10:00
md_5
c615b2362f Revert "Case shouldn't really matter for groups and permissions"
This reverts commit a67d4a1697.
2013-04-14 11:57:50 +10:00
md_5
5620c4679d Fix issue #262 and put all scoreboard bugs to bed 2013-04-14 09:33:36 +10:00
md_5
a9ad4889f7 Revert "Bungee join / part messages"
This reverts commit 7108bd4deb.
2013-04-13 18:28:11 +10:00
md_5
7108bd4deb Bungee join / part messages 2013-04-13 18:17:08 +10:00
md_5
ac1119bef8 Move pending connects check 2013-04-13 11:51:34 +10:00
md_5
dac259933b Add pending connects for bad plugin message based connects 2013-04-13 09:49:08 +10:00
md_5
a57ae83d62 Return from method when we can't connect 2013-04-13 09:46:21 +10:00
md_5
a00c91c03a Fix users showing as disconnected on global ping list 2013-04-13 09:37:56 +10:00
md_5
a67d4a1697 Case shouldn't really matter for groups and permissions 2013-04-12 19:41:20 +10:00
md_5
b87fff2614 Fix users being disconnected without the event q_q 2013-04-12 12:43:17 +10:00
md_5
0c144c38db Fix issue #251 - hidden servers in list command 2013-04-12 12:40:36 +10:00
md_5
5592f81e97 Proper equals on servers 2013-04-12 08:22:17 +10:00
md_5
5d1a2c59a7 ConcurrentHashMap is junk - lets stick to standard unless issues arise. 2013-04-11 20:32:49 +10:00
md_5
88c99f071e Reduce ram usage by ~65kb / player. See #229 2013-04-11 20:30:07 +10:00
md_5
19c3c23b86 Reformat all code 2013-04-11 20:26:55 +10:00
Björn Teichmann
5dfe83cf6d add fallback_server so we can still have a different default server on first join 2013-04-11 20:23:30 +10:00
Steve Anton
fecaf76acf Initialize depends set to an empty set to prevent a NPE when no depends given. 2013-04-11 11:21:35 +10:00
Steve Anton
c8c1028bd9 Implement support for plugin hard dependencies. 2013-04-11 09:10:40 +10:00
md-5
51be33dbd7 Merge pull request #261 from roblabla/patch-2
Fix LoginEvent not being called.
2013-04-10 14:47:29 -07:00
Robin Lambertz
8cac038a07 Fix LoginEvent not being called.
If the server is in offline mode, the LoginEvent should be called.
2013-04-10 14:47:57 +02:00
md-5
edceaf072c Merge pull request #259 from roblabla/patch-2
Fix Crash with Teams. For real this time.
2013-04-08 17:26:10 -07:00
Robin Lambertz
f935f93d98 Fix two small typos. 2013-04-09 01:18:00 +01:00
Robin Lambertz
e5c457df04 Add new Team instances to the list of teams. 2013-04-09 02:10:41 +02:00
md-5
d4f60e65c3 Merge pull request #258 from roblabla/patch-2
Fix the crash with teams not being cleared
2013-04-08 16:25:05 -07:00
Robin Lambertz
548b2f2c60 Fix the crash with teams not being cleared
You forgot to actually add the team to the list of teams...
2013-04-09 01:23:08 +02:00
md-5
3733ecf628 Merge pull request #250 from weaondara/master
check the player's server before connecting
2013-04-04 14:06:40 -07:00
weaondara
6e9cdb2e20 send message to player if target is current server 2013-04-04 18:44:46 +03:00
weaondara
d900a5eae7 setDisplayName bug fix
first remove
then change and add again
2013-04-02 12:18:43 +03:00
weaondara
68712ab854 check the player's server before really connecting
otherwise the player will be kicked with the message "Logged in from another location" if the target server is the current server
2013-04-02 02:13:29 +03:00
md-5
d067662967 Merge pull request #245 from weaondara/master
add permission to check in PermissionCheckEvent
2013-03-31 19:04:14 -07:00
weaondara
88a52bc4e9 set displayName in function setDisplayName 2013-03-31 16:25:53 +03:00
weaondara
d684f5de69 added permission in constructor 2013-03-31 12:36:21 +03:00
weaondara
a316d6ccdc Update PermissionCheckEvent.java 2013-03-31 12:30:46 +03:00
md_5
5de8ac89e5 Remove any scores before updating - see #238 2013-03-29 08:08:13 +11:00
md_5
8707995503 Nullcheck getServer on failed reconnect handler to account for failings on first connect 2013-03-28 22:24:56 +11:00
md_5
caea1e3fa5 Set server as obsolete when kicked. 2013-03-28 19:53:04 +11:00
md_5
4096012d8e Bump to netty release 2013-03-27 17:39:42 +11:00
md_5
acbf6d3137 Add current server check to failed listener too 2013-03-26 18:12:28 +11:00
md_5
3a3fb27d9a Fixx issue #228 and #188 - CSV classes and ALL target for player list 2013-03-26 17:53:36 +11:00
md-5
e506957d38 Fix current server check - #216 2013-03-26 11:50:36 +11:00
md_5
8003dc50c4 Fix #227 - don't colour first name on list 2013-03-25 19:25:06 +11:00
md_5
75bc2738b9 Check length before checking if command. 2013-03-24 16:32:54 +11:00
md_5
cf4846baa9 Add issue #221 - permission check event 2013-03-24 16:29:45 +11:00
md_5
120a4dc401 Remove HTTP request which snuck in. 2013-03-24 16:25:53 +11:00
md_5
6881597692 Fix issue #156 - actually register packet for reading 2013-03-24 11:53:07 +11:00
md_5
0ffb557557 Try my artistic vision on the list command. 2013-03-24 11:44:30 +11:00
md_5
5f7ecf9d3b Fix issue #213 - servers crashing clients. 2013-03-24 11:39:14 +11:00
md_5
e07f1e603a Lowercase forced server checks - #223 2013-03-24 11:02:24 +11:00
md_5
430b5ff392 Bump expirary date back to 26/4/2013 2013-03-24 09:00:32 +11:00
md_5
81fe547a7c Server admins see stack trace and die. See #225 2013-03-24 08:41:25 +11:00
md_5
04fb1df3e1 Don't null out scores after objectives. 2013-03-23 20:58:21 +11:00
md_5
73aaf58009 Try and make the client not crash when switching teams. @lazertester 2013-03-23 20:49:47 +11:00
md_5
fd062503e1 Skeleton team object. TODO: Make @mbax come and show me how to rewrite entire API. Also interface 2013-03-23 20:41:35 +11:00
md_5
c97f113497 Add team packet. 2013-03-23 20:32:37 +11:00
md_5
14fcb90395 Fire server kick for logins too 2013-03-23 19:38:00 +11:00
md_5
39009d8c96 Check players have a server before sending 2013-03-23 13:45:22 +11:00
md_5
13f394b9a7 Send the players real IP for use in Spigot servers. 2013-03-23 11:24:37 +11:00
md_5
9838a09a8c Add slightly modified version of #220 - ChatColor.getByChar 2013-03-21 21:29:26 +11:00
md_5
ffddcf939f Rewrite cancel method as it is posing some issues 2013-03-21 20:16:41 +11:00
md_5
3d8143c36e Use Bungee thread pool for additional logging 2013-03-21 16:28:30 +11:00
md_5
5e31b158e9 Work around JDK stupidity with regards to thread count 2013-03-21 16:25:05 +11:00
md_5
a59e0f0b6b Async close http client as it appears bugged 2013-03-21 13:54:40 +11:00
md_5
517655f54e How did that debug stay there? 2013-03-21 13:45:56 +11:00
md_5
55ec76beee Allow users to connect 2013-03-21 13:37:38 +11:00
md_5
a564d4c7f1 Fix issue #219 - make the scheduler work 2013-03-21 13:35:35 +11:00
md_5
86b864ce21 Fix small javadoc warning 2013-03-20 20:36:33 +11:00
md_5
6813b82b84 Fix issue #216 and #219 by moving current server check to the connect method. 2013-03-20 20:25:42 +11:00
md_5
27d454524f Use asynchttpclient instead 2013-03-20 19:10:59 +11:00
md_5
8827feacfb Implement high performance HTTP api for plugins with jetty. 2013-03-20 18:52:26 +11:00
md_5
692610cd7e Add asynchronous event API from issue #200 2013-03-19 20:09:15 +11:00
md_5
1edd27963f Add issue #215 - scheduler API 2013-03-19 19:45:34 +11:00
md_5
730c05aaad Actually print time it took 2013-03-19 17:23:07 +11:00
md_5
2bae6cafc2 Try and prevent inefficient plugins by nagging when events take longer than 0.25ms to process. Someone let me know if this spams and I need to increase the max time. 2013-03-19 17:21:54 +11:00
md_5
5a15d5387c Use as many threads as the system can handle. 2013-03-19 16:07:39 +11:00
md_5
3fed94fbf2 Stop trying to be Forge for now. 2013-03-19 12:07:59 +11:00
md_5
1dd661f619 Use a byte array in the ping handler for simplicity 2013-03-19 12:07:13 +11:00
md_5
e364cff44e Fix loading config. 2013-03-19 12:00:57 +11:00
md_5
dfa47f740d Add feature #208 - Bungee texture packs. 2013-03-19 11:54:13 +11:00
md_5
87fcef0658 Implement PR #212 - sendMessages() for CommandSenders 2013-03-19 11:43:37 +11:00
md_5
49f2f5f28b Fix issue #211 - ServerInfo.ping 2013-03-19 11:40:56 +11:00
md_5
a12debf6d0 Revert "Add info on registered channels to /bungee to enable users to help identify cause of random timeouts & resource leaks"
This reverts commit b3c34815b0.
2013-03-18 17:36:57 +11:00
md_5
5d3cb452fe Fix issue #206 by updating netty. Big shoutout to Trustin and the Netty project. 2013-03-18 17:36:41 +11:00
md_5
b3c34815b0 Add info on registered channels to /bungee to enable users to help identify cause of random timeouts & resource leaks 2013-03-17 09:03:20 +11:00
md_5
40768c1711 Update Netty to 4.0.0-Beta3 2013-03-17 09:01:12 +11:00
md_5
b16da7d048 Be sure to null serverSentScoreboard each connect. 2013-03-16 22:35:13 +11:00
md_5
e681c8906d Add server kick event 2013-03-16 21:32:11 +11:00
md_5
ce40391717 Untested attempt at sending users to default server when their server goes down. 2013-03-16 21:17:41 +11:00
md_5
2e51ec4fba *add else statement to prevent duplicate exception logging 2013-03-16 21:09:16 +11:00
md_5
54098c8989 Attempt to track, and remove scoreboards at reconnect 2013-03-16 21:06:28 +11:00
md_5
156ea30c32 Add scoreboard packets + API classes. Still unimplemented. 2013-03-16 20:48:51 +11:00
md_5
34f4bae923 Fix score add definition 2013-03-16 20:37:36 +11:00
md_5
6bb089074e Clear scoreboards on reconnect. 2013-03-16 20:13:01 +11:00
md_5
70c73211a0 Fix remapping of entities (fishing floats) 2013-03-16 13:07:51 +11:00
md_5
c059345802 Add send command, closes issue #197 2013-03-16 12:14:15 +11:00
md_5
f5b4e1242d Add #205 more methods in Plugin for getting resources / data folders. 2013-03-16 11:53:25 +11:00
md_5
5365e5fb92 Bump the other pom versions to 1.5 too. 2013-03-15 21:21:58 +11:00
md_5
dc2ef1eac7 Bump api to 1.5 2013-03-15 21:19:10 +11:00
md_5
113dada511 Depreceate old register command / listener methods. 2013-03-15 21:08:50 +11:00
md_5
8ea5205fef Reuse single packet instance to save overhead 2013-03-15 21:03:45 +11:00
md_5
05d76c3f67 Pretty up login fail message. Fixes #201 2013-03-15 21:01:35 +11:00
md_5
d6e29b3f29 Fix issue #203 - errors in Util.exception when no trace present. 2013-03-15 20:38:40 +11:00
md_5
bd479ba083 Remove outdated $() logger getter. 2013-03-14 21:33:22 +11:00
md_5
704fe11b05 Make forced hosts take higher priority than force default. Closes #184 2013-03-14 21:18:59 +11:00
md_5
4811e7be4f Implement PostLoginEvent, closes #191 2013-03-14 21:11:16 +11:00
md_5
ac426f0c3f Use global netty version variable. 2013-03-14 20:35:58 +11:00
md_5
0f30024040 Now that we don't deploy the proxy we don't need the shaded identifier 2013-03-14 20:28:24 +11:00
md_5
5bfab582df Fix #156 - Bukkit trying to get client settings. 2013-03-14 20:22:26 +11:00
md_5
ec1de0f636 Add global slot limit. Fixes feature request #40 2013-03-14 20:14:01 +11:00
md_5
9bf6a58ba2 Update snakeyaml and guava versions. 2013-03-14 19:51:36 +11:00
md_5
9483c0228b Make protocol only depend on netty-buffer 2013-03-14 19:50:44 +11:00
md_5
09bb7a93d2 Update MySQL version 2013-03-14 19:50:17 +11:00
md_5
8935e77118 Use the Netty byte array encoder now that it is fixed. 2013-03-14 19:49:31 +11:00
md_5
b71d253de2 Clean up cipher codec for maximum speed and minimal memory copy. 2013-03-14 19:39:03 +11:00
md_5
d54f2462a8 Less error for IOExceptions, more debug for exceptions in the exception handler. 2013-03-14 19:22:54 +11:00
md_5
c1ff4ffb89 Update to 1.5 & don't deploy proxy to maven, only APIs 2013-03-14 17:50:33 +11:00
md_5
58f1ab208b Reenable encryption + online mode. 2013-03-14 17:46:57 +11:00
md_5
d5f25b07e5 *remove wrapper class 2013-03-14 17:25:24 +11:00
md_5
30b381853c Change packet handling from ByteBufs to byte arrays to work around netty bug. Connection now appears to be stable - just need to add an optimized encryption algorithm back. 2013-03-14 17:24:32 +11:00
md_5
b0820208e6 @normanm @trustin This hack appears to fix all my issues with writing ByteBufs directly to channels. new @lazertester.party(). Others, please note this still isn't even a beta quality build. Due to memory laziness this build will only accept one connection. 2013-03-13 20:11:41 +11:00
md_5
bc0a076e4b Don't sync, its an optimization - right? 2013-03-13 18:08:43 +11:00
md_5
1763dd3078 Remove unneeded channel option. Still trying to fix all these disconnect errors though. 2013-03-13 18:07:07 +11:00
md_5
373ec187fb Fix /server command to only show server name 2013-03-12 18:07:25 +11:00
md_5
ac2c96c2ea This test WITHOUT encryption reveals something is majorly wrong with our packet decoding causing random and frequent disconnects. 2013-03-12 17:53:18 +11:00
md_5
5688099605 Also make sure we get the restricted value per server 2013-03-12 17:14:50 +11:00
md_5
1d2afae98e Fix canAccess check 2013-03-12 17:13:50 +11:00
md_5
4805087e38 Case insensitive sort 2013-03-12 17:11:06 +11:00
md_5
e2f134ec08 Actually alphabetize players - thanks @Grooohm for the good spot! 2013-03-12 16:56:09 +11:00
md_5
92c1450909 Fix issue #129 - make chat event fire for commands, and add isCommand method. 2013-03-12 15:15:25 +11:00
md_5
fea3642550 Add #183 - restricted servers 2013-03-12 12:13:23 +11:00
md_5
f02d17c979 Fix issue #55 - show alerts in console 2013-03-12 12:03:21 +11:00
md_5
3dac86a94c Add getConsole() to the API 2013-03-12 12:01:47 +11:00
md_5
758e196479 Rewrite list command for per server breakdown, and alphabetizing. 2013-03-12 11:58:04 +11:00
md_5
5dd3384b31 Add GetServer channel, see #145 2013-03-12 11:43:37 +11:00
md_5
59efec128d Add ProxiedPlayer.chat, see #146 2013-03-12 11:38:11 +11:00
md_5
3b90737273 Update CipherCodec to try and fix issues. 2013-03-12 11:21:39 +11:00
md_5
f9f664f9b6 Try using out.writerIndex instead of readerIndex 2013-03-11 20:27:19 +11:00
md_5
9edcda7ace Downgrade NIO to 1.4.7 2013-03-11 18:28:10 +11:00
md_5
cd1420d9cd Fix entity rewrites on falling blocks. 2013-03-11 14:31:51 +11:00
md_5
a3e1493ce1 Merge NIO into master. I would not recommend this on a production server at all. Its 1.5 anyway. 2013-03-11 13:30:29 +11:00
md_5
9fb814003b Handle kicks when trying to login to server. Now we have to be about done. 2013-03-11 10:15:14 +11:00
md_5
76319371f7 Small code / api cleanup. Need to fix kicking in general, especially in ServerConnector before release. 2013-03-11 10:06:14 +11:00
md_5
2e33ab1460 Remove outdated javadoc 2013-03-11 09:52:53 +11:00
md_5
cfd10f5714 Handle when user gets kicked midway through login to other server 2013-03-11 09:47:42 +11:00
md_5
6ae5feee60 Error handling when cannot connect to server. 2013-03-11 09:39:37 +11:00
md_5
0b7b87d9c3 Wait 100ms before closing socket to give server time to process. 2013-03-11 09:30:49 +11:00
md_5
fa5804ec86 OMG WTF BBQ IT WORKS. LETS IRON OUT BUGS AND GET OUR RELEASE TARGET, ONLY 12 HOURS LEFT. 2013-03-11 09:18:39 +11:00
md_5
6e18403cd9 Ensure all resources are closed all the time. 2013-03-11 09:11:36 +11:00
md_5
f67ad024f5 Make builds expire. 2013-03-10 20:01:30 +11:00
md_5
a0989a8932 Update for 1.5. This build only supports 1.5, not any other versions. 2013-03-10 12:41:13 +11:00
md_5
3be83cb5a4 Disconnect when exception caught 2013-03-09 17:59:44 +11:00
md_5
0ce02251d4 Start work on a little spit and polish, with cleaner exceptions. 2013-03-09 16:47:13 +11:00
md_5
b4d104d258 *actually compile (wire up the broadcast method) 2013-03-09 14:31:39 +11:00
md_5
0c69814db7 Make sendMessage work again 2013-03-09 14:29:54 +11:00
md_5
f0766ebcfb Bridge packets both ways. 2013-03-09 14:27:24 +11:00
md_5
f0a19e0f45 Small cleanup of packet classes 2013-03-09 14:23:40 +11:00
md_5
6525502836 We can login now. Kindof. *wipes sweat off brow. 2013-03-09 14:19:12 +11:00
md_5
504f703fbb This is so tiring 2013-03-09 12:35:32 +11:00
md_5
4fb85721a9 Nearly ready to get a working connection, however few hacks due to own shortcomings and netty shortcomings. 2013-03-09 12:08:26 +11:00
md_5
e12bc1d92e Closer to connecting, trying to figure out this encryption bug also present in first connect on Spigot 2013-03-09 10:47:12 +11:00
md_5
bcaafc206f Advancing further in the login process. 2013-03-09 10:10:36 +11:00
md_5
8a96555cc7 We can now get pings! 2013-03-09 09:54:19 +11:00
md_5
45c848a4fd Cleanup channel initialization - now its time to get this show working. 2013-03-09 09:45:10 +11:00
md_5
9e0ae0a70d Thread login auth properly. 2013-03-08 18:26:59 +11:00
md_5
9ad9003974 Tigthen access + javadocs on a few netty related classes. 2013-03-08 18:07:21 +11:00
md_5
e18fe49cf9 Whats this? It compiles. Doesn't mean it near works though. 2013-03-08 17:52:17 +11:00
md_5
c3d702a5b3 Switch main packet interface to bytebuf 2013-03-08 17:35:01 +11:00
md_5
cdf26f7950 Nearing a compilale state, gonna have to redo the bootstrap. 2013-03-08 17:24:09 +11:00
md_5
644deee3c6 Redo parts of login and connection sequences 2013-03-07 21:33:49 +11:00
md_5
b6e76f4054 Cleanup permissions and disconnect sequences 2013-03-07 21:04:03 +11:00
md_5
0f9cc76633 Update Server interface to remove depreceated methods 2013-03-07 20:52:19 +11:00
md_5
0afc52c130 Upstream and downstream bridges are now uber sexy 2013-03-07 20:47:39 +11:00
md_5
0077af58d0 Start work on netty overhaul 2013-03-07 20:05:41 +11:00
110 changed files with 3697 additions and 1478 deletions

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.4.7-SNAPSHOT</version> <version>1.5-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.4.7-SNAPSHOT</version> <version>1.5-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>BungeeCord-API</name> <name>BungeeCord-API</name>
@@ -22,13 +22,19 @@
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<version>13.0.1</version> <version>14.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.ning</groupId>
<artifactId>async-http-client</artifactId>
<version>1.7.14</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.yaml</groupId> <groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
<version>1.11</version> <version>1.12</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.eventbus;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.UncheckedExecutionException;
import java.lang.reflect.Method;
import java.util.Set;
/**
* A {@link HandlerFindingStrategy} for collecting all event handler methods that are marked with
* the {@link Subscribe} annotation.
*
* @author Cliff Biffle
* @author Louis Wasserman
*/
class AnnotatedHandlerFinder implements HandlerFindingStrategy {
/**
* A thread-safe cache that contains the mapping from each class to all methods in that class and
* all super-classes, that are annotated with {@code @Subscribe}. The cache is shared across all
* instances of this class; this greatly improves performance if multiple EventBus instances are
* created and objects of the same class are registered on all of them.
*/
private static final LoadingCache<Class<?>, ImmutableList<Method>> handlerMethodsCache =
CacheBuilder.newBuilder()
.weakKeys()
.build(new CacheLoader<Class<?>, ImmutableList<Method>>() {
@Override
public ImmutableList<Method> load(Class<?> concreteClass) throws Exception {
return getAnnotatedMethodsInternal(concreteClass);
}
});
/**
* {@inheritDoc}
*
* This implementation finds all methods marked with a {@link Subscribe} annotation.
*/
@Override
public Multimap<Class<?>, EventHandler> findAllHandlers(Object listener) {
Multimap<Class<?>, EventHandler> methodsInListener = HashMultimap.create();
Class<?> clazz = listener.getClass();
for (Method method : getAnnotatedMethods(clazz)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> eventType = parameterTypes[0];
EventHandler handler = new EventHandler(listener, method);
methodsInListener.put(eventType, handler);
}
return methodsInListener;
}
private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {
try {
return handlerMethodsCache.getUnchecked(clazz);
} catch (UncheckedExecutionException e) {
throw Throwables.propagate(e.getCause());
}
}
private static ImmutableList<Method> getAnnotatedMethodsInternal(Class<?> clazz) {
Set<? extends Class<?>> supers = TypeToken.of(clazz).getTypes().rawTypes();
ImmutableList.Builder<Method> result = ImmutableList.builder();
for (Method method : clazz.getMethods()) {
/*
* Iterate over each distinct method of {@code clazz}, checking if it is annotated with
* @Subscribe by any of the superclasses or superinterfaces that declare it.
*/
for (Class<?> c : supers) {
try {
Method m = c.getMethod(method.getName(), method.getParameterTypes());
if (m.isAnnotationPresent(Subscribe.class)) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new IllegalArgumentException("Method " + method
+ " has @Subscribe annotation, but requires " + parameterTypes.length
+ " arguments. Event handler methods must require a single argument.");
}
Class<?> eventType = parameterTypes[0];
result.add(method);
break;
}
} catch (NoSuchMethodException ignored) {
// Move on.
}
}
}
return result.build();
}
}

View File

@@ -1,5 +1,7 @@
package net.md_5.bungee.api; package net.md_5.bungee.api;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
@@ -105,13 +107,30 @@ public enum ChatColor
* Pattern to remove all colour codes. * Pattern to remove all colour codes.
*/ */
private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile( "(?i)" + String.valueOf( COLOR_CHAR ) + "[0-9A-FK-OR]" ); private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile( "(?i)" + String.valueOf( COLOR_CHAR ) + "[0-9A-FK-OR]" );
/**
* Colour instances keyed by their active character.
*/
private static final Map<Character, ChatColor> BY_CHAR = new HashMap<>();
/**
* The code appended to {@link #COLOR_CHAR} to make usable colour.
*/
private final char code;
/** /**
* This colour's colour char prefixed by the {@link #COLOR_CHAR}. * This colour's colour char prefixed by the {@link #COLOR_CHAR}.
*/ */
private final String toString; private final String toString;
static
{
for ( ChatColor colour : values() )
{
BY_CHAR.put( colour.code, colour );
}
}
private ChatColor(char code) private ChatColor(char code)
{ {
this.code = code;
this.toString = new String( new char[] this.toString = new String( new char[]
{ {
COLOR_CHAR, code COLOR_CHAR, code
@@ -153,4 +172,15 @@ public enum ChatColor
} }
return new String( b ); return new String( b );
} }
/**
* Get the colour represented by the specified code.
*
* @param code the code to search for
* @return the mapped colour, or null if non exists
*/
public static ChatColor getByChar(char code)
{
return BY_CHAR.get( code );
}
} }

View File

@@ -19,6 +19,14 @@ public interface CommandSender
*/ */
public void sendMessage(String message); public void sendMessage(String message);
/**
* Send several messages to this sender. Each message will be sent
* separately.
*
* @param messages the messages to send
*/
public void sendMessages(String... messages);
/** /**
* Get all groups this user is part of. This returns an unmodifiable * Get all groups this user is part of. This returns an unmodifiable
* collection. * collection.

View File

@@ -2,6 +2,8 @@ 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.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@@ -12,6 +14,7 @@ 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.connection.Server; import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.scheduler.TaskScheduler;
public abstract class ProxyServer public abstract class ProxyServer
{ {
@@ -208,7 +211,40 @@ 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 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); public abstract ServerInfo constructServerInfo(String name, InetSocketAddress address, boolean restricted);
/**
* Returns the console overlord for this proxy. Being the console, this
* command server cannot have permissions or groups, and will be able to
* execute anything.
*
* @return the console command sender of this proxy
*/
public abstract CommandSender getConsole();
/**
* Return the folder used to load plugins from.
*
* @return the folder used to load plugin
*/
public abstract File getPluginsFolder();
/**
* Get the scheduler instance for this proxy.
*
* @return the in use scheduler
*/
public abstract TaskScheduler getScheduler();
/**
* 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();
} }

View File

@@ -32,6 +32,11 @@ public class ListenerInfo
* Name of the server which users will be taken to by default. * Name of the server which users will be taken to by default.
*/ */
private final String defaultServer; private final String defaultServer;
/**
* Name of the server which users will be taken when current server goes
* down.
*/
private final String fallbackServer;
/** /**
* Whether reconnect locations will be used, or else the user is simply * Whether reconnect locations will be used, or else the user is simply
* transferred to the default server on connect. * transferred to the default server on connect.
@@ -42,4 +47,9 @@ 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;
} }

View File

@@ -1,69 +1,49 @@
package net.md_5.bungee.api.config; package net.md_5.bungee.api.config;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Synchronized;
import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ServerPing; import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
/** /**
* Class used to represent a server to connect to. * Class used to represent a server to connect to.
*/ */
@Data public interface ServerInfo
@AllArgsConstructor
public abstract class ServerInfo
{ {
/** /**
* Name this server displays as. * Get the name of this server.
*
* @return the configured name for this server address
*/ */
private final String name; String getName();
/**
* Connectable address of this server.
*/
private final InetSocketAddress address;
/**
* Players connected to a server defined by these properties.
*/
private final Collection<ProxiedPlayer> players = new ArrayList<>();
/** /**
* Add a player to the internal set of this server. * Gets the connectable host + port pair for this server. Implementations
* expect this to be used as the unique identifier per each instance of this
* class.
* *
* @param player the player to add * @return the IP and port pair for this server
*/ */
@Synchronized("players") InetSocketAddress getAddress();
public void addPlayer(ProxiedPlayer player)
{
players.add( player );
}
/**
* Remove a player form the internal set of this server.
*
* @param player the player to remove
*/
@Synchronized("players")
public void removePlayer(ProxiedPlayer player)
{
players.remove( player );
}
/** /**
* Get the set of all players on this server. * Get the set of all players on this server.
* *
* @return an unmodifiable collection of all players on this server * @return an unmodifiable collection of all players on this server
*/ */
@Synchronized("players") Collection<ProxiedPlayer> getPlayers();
public Collection<ProxiedPlayer> getPlayers()
{ /**
return Collections.unmodifiableCollection( players ); * Whether the player can access this server. It will only return false when
} * the player has no permission and this server is restricted.
*
* @param sender the player to check access for
* @return whether access is granted to this server
*/
boolean canAccess(CommandSender sender);
/** /**
* Send data by any available means to this server. * Send data by any available means to this server.
@@ -71,12 +51,12 @@ public abstract class ServerInfo
* @param channel the channel to send this data via * @param channel the channel to send this data via
* @param data the data to send * @param data the data to send
*/ */
public abstract void sendData(String channel, byte[] data); void sendData(String channel, byte[] data);
/** /**
* Asynchronously gets the current player count on this server. * Asynchronously gets the current player count on this server.
* *
* @param callback the callback to call when the count has been retrieved. * @param callback the callback to call when the count has been retrieved.
*/ */
public abstract void ping(Callback<ServerPing> callback); void ping(Callback<ServerPing> callback);
} }

View File

@@ -0,0 +1,17 @@
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

@@ -16,4 +16,14 @@ public interface Connection
* @return the remote address * @return the remote address
*/ */
public InetSocketAddress getAddress(); public InetSocketAddress getAddress();
/**
* Disconnects this end of the connection for the specified reason. If this
* is an {@link ProxiedPlayer} the respective server connection will be
* closed too.
*
* @param reason the reason shown to the player / sent to the server on
* disconnect
*/
public void disconnect(String reason);
} }

View File

@@ -30,14 +30,6 @@ public interface PendingConnection extends Connection
*/ */
public InetSocketAddress getVirtualHost(); public InetSocketAddress getVirtualHost();
/**
* Completely kick this user from the proxy and all of its child
* connections.
*
* @param reason the disconnect reason displayed to the player
*/
public void disconnect(String reason);
/** /**
* Get the listener that accepted this connection. * Get the listener that accepted this connection.
* *

View File

@@ -48,13 +48,6 @@ public interface ProxiedPlayer extends Connection, CommandSender
*/ */
public int getPing(); public int getPing();
/**
* Disconnect (remove) this player from the proxy with the specified reason.
*
* @param reason the reason displayed to the player
*/
public void disconnect(String reason);
/** /**
* Send a plugin message to this player. * Send a plugin message to this player.
* *
@@ -69,4 +62,11 @@ public interface ProxiedPlayer extends Connection, CommandSender
* @return the pending connection that this player used * @return the pending connection that this player used
*/ */
public PendingConnection getPendingConnection(); public PendingConnection getPendingConnection();
/**
* Make this player chat (say something), to the server he is currently on.
*
* @param message the message to say
*/
public void chat(String message);
} }

View File

@@ -1,7 +1,5 @@
package net.md_5.bungee.api.connection; package net.md_5.bungee.api.connection;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
/** /**
@@ -24,14 +22,4 @@ public interface Server extends Connection
* @param data the data to send * @param data the data to send
*/ */
public abstract void sendData(String channel, byte[] data); public abstract void sendData(String channel, byte[] data);
/**
* Asynchronously gets the current player count on this server.
*
* @param callback the callback to call when the count has been retrieved.
* @deprecated use the corresponding method in {@link ServerInfo} for
* clarity
*/
@Deprecated
public abstract void ping(Callback<ServerPing> callback);
} }

View File

@@ -0,0 +1,75 @@
package net.md_5.bungee.api.event;
import com.google.common.base.Preconditions;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.plugin.Event;
import net.md_5.bungee.api.plugin.Plugin;
/**
* Represents an event which depends on the result of asynchronous operations.
*
* @param <T> Type of this event
*/
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class AsyncEvent<T> extends Event
{
private final Callback<T> done;
private final Set<Plugin> intents = Collections.newSetFromMap( new ConcurrentHashMap<Plugin, Boolean>() );
private final AtomicBoolean fired = new AtomicBoolean();
private final AtomicInteger latch = new AtomicInteger();
@Override
@SuppressWarnings("unchecked")
public void postCall()
{
fired.set( true );
if ( latch.get() == 0 )
{
done.done( (T) this, null );
}
}
/**
* Register an intent that this plugin will continue to perform work on a
* background task, and wishes to let the event proceed once the registered
* background task has completed.
*
* @param plugin the plugin registering this intent
*/
public void registerIntent(Plugin plugin)
{
Preconditions.checkState( !fired.get(), "Event %s has already been fired", this );
Preconditions.checkState( !intents.contains( plugin ), "Plugin %s already registered intent for event %s", plugin, this );
intents.add( plugin );
latch.incrementAndGet();
}
/**
* Notifies this event that this plugin has done all its required processing
* and wishes to let the event proceed.
*
* @param plugin a plugin which has an intent registered for this event
*/
@SuppressWarnings("unchecked")
public void completeIntent(Plugin plugin)
{
Preconditions.checkState( intents.contains( plugin ), "Plugin %s has not registered intent for event %s", plugin, this );
intents.remove( plugin );
if ( latch.decrementAndGet() == 0 )
{
done.done( (T) this, null );
}
}
}

View File

@@ -30,4 +30,14 @@ public class ChatEvent extends TargetedEvent implements Cancellable
super( sender, receiver ); super( sender, receiver );
this.message = message; this.message = message;
} }
/**
* Checks whether this message is valid as a command
*
* @return if this message is a command
*/
public boolean isCommand()
{
return message.length() > 0 && message.charAt( 0 ) == '/';
}
} }

View File

@@ -3,9 +3,9 @@ package net.md_5.bungee.api.event;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.plugin.Cancellable; import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
/** /**
* Event called to represent a player logging in. * Event called to represent a player logging in.
@@ -13,7 +13,7 @@ import net.md_5.bungee.api.plugin.Event;
@Data @Data
@ToString(callSuper = false) @ToString(callSuper = false)
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class LoginEvent extends Event implements Cancellable public class LoginEvent extends AsyncEvent<LoginEvent> implements Cancellable
{ {
/** /**
@@ -28,4 +28,10 @@ public class LoginEvent extends Event implements Cancellable
* Connection attempting to login. * Connection attempting to login.
*/ */
private final PendingConnection connection; private final PendingConnection connection;
public LoginEvent(PendingConnection connection, Callback<LoginEvent> done)
{
super( done );
this.connection = connection;
}
} }

View File

@@ -0,0 +1,40 @@
package net.md_5.bungee.api.event;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Event;
/**
* Called when the permission of a CommandSender is checked.
*/
@Data
@AllArgsConstructor
@ToString(callSuper = false)
@EqualsAndHashCode(callSuper = false)
public class PermissionCheckEvent extends Event
{
/**
* The command sender being checked for a permission.
*/
private final CommandSender sender;
/**
* The permission to check.
*/
private final String permission;
/**
* The outcome of this permission check.
*/
@Getter(AccessLevel.NONE)
private boolean hasPermission;
public boolean hasPermission()
{
return hasPermission;
}
}

View File

@@ -0,0 +1,23 @@
package net.md_5.bungee.api.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;
/**
* Event called as soon as a connection has an {@link ProxiedPlayer} and is
* ready to be connected to a server.
*/
@Data
@ToString(callSuper = false)
@EqualsAndHashCode(callSuper = false)
public class PostLoginEvent extends Event
{
/**
* The player involved with this event.
*/
private final ProxiedPlayer player;
}

View File

@@ -0,0 +1,43 @@
package net.md_5.bungee.api.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
/**
* Represents a player getting kicked from a server.
*/
@Data
@ToString(callSuper = false)
@EqualsAndHashCode(callSuper = false)
public class ServerKickEvent extends Event implements Cancellable
{
/**
* Cancelled status.
*/
private boolean cancelled;
/**
* Player being kicked.
*/
private final ProxiedPlayer player;
/**
* Kick reason.
*/
private String kickReason;
/**
* Server to send player to if this event is cancelled.
*/
private ServerInfo cancelServer;
public ServerKickEvent(ProxiedPlayer player, String kickReason, ServerInfo cancelServer)
{
this.player = player;
this.kickReason = kickReason;
this.cancelServer = cancelServer;
}
}

View File

@@ -5,4 +5,11 @@ package net.md_5.bungee.api.plugin;
*/ */
public abstract class Event public abstract class Event
{ {
/**
* Method called after this event has been dispatched to all handlers.
*/
public void postCall()
{
}
} }

View File

@@ -1,6 +1,9 @@
package net.md_5.bungee.api.plugin; package net.md_5.bungee.api.plugin;
import java.io.File;
import java.io.InputStream;
import lombok.Getter; import lombok.Getter;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ConfigurationAdapter; import net.md_5.bungee.api.config.ConfigurationAdapter;
/** /**
@@ -12,6 +15,10 @@ public class Plugin
@Getter @Getter
private PluginDescription description; private PluginDescription description;
@Getter
private ProxyServer proxy;
@Getter
private File file;
/** /**
* Called when the plugin has just been loaded. Most of the proxy will not * Called when the plugin has just been loaded. Most of the proxy will not
@@ -36,13 +43,40 @@ public class Plugin
{ {
} }
/**
* Gets the data folder where this plugin may store arbitrary data. It will
* be a child of {@link ProxyServer#getPluginsFolder()}.
*
* @return the data folder of this plugin
*/
public final File getDataFolder()
{
return new File( getProxy().getPluginsFolder(), getDescription().getName() );
}
/**
* Get a resource from within this plugins jar or container. Care must be
* taken to close the returned stream.
*
* @param name the full path name of this resource
* @return the stream for getting this resource, or null if it does not
* exist
*/
public final InputStream getResourceAsStream(String name)
{
return getClass().getClassLoader().getResourceAsStream( name );
}
/** /**
* Called by the loader to initialize the fields in this plugin. * Called by the loader to initialize the fields in this plugin.
* *
* @param description the description that describes this plugin * @param description the description that describes this plugin
* @param jarfile this plugins jar or container
*/ */
final void init(PluginDescription description) final void init(ProxyServer proxy, PluginDescription description, File file)
{ {
this.proxy = proxy;
this.description = description; this.description = description;
this.file = file;
} }
} }

View File

@@ -45,6 +45,6 @@ public class PluginClassloader extends URLClassLoader
} }
} }
} }
throw new ClassNotFoundException(name); throw new ClassNotFoundException( name );
} }
} }

View File

@@ -1,12 +1,16 @@
package net.md_5.bungee.api.plugin; package net.md_5.bungee.api.plugin;
import java.util.HashSet;
import java.util.Set;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
/** /**
* POJO representing the plugin.yml file. * POJO representing the plugin.yml file.
*/ */
@Data @Data
@NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class PluginDescription public class PluginDescription
{ {
@@ -27,8 +31,8 @@ public class PluginDescription
* Plugin author. * Plugin author.
*/ */
private String author; private String author;
/**
public PluginDescription() * Plugin hard dependencies.
{ */
} private Set<String> depends = new HashSet<>();
} }

View File

@@ -11,24 +11,30 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Stack;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
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.event.LoginEvent;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
/** /**
* Class to manage bridging between plugin duties and implementation duties, for * Class to manage bridging between plugin duties and implementation duties, for
* example event handling and plugin management. * example event handling and plugin management.
*/ */
@RequiredArgsConstructor
public class PluginManager public class PluginManager
{ {
private static final Pattern argsSplit = Pattern.compile( " " ); private static final Pattern argsSplit = Pattern.compile( " " );
/*========================================================================*/ /*========================================================================*/
private final ProxyServer proxy;
/*========================================================================*/
private final Yaml yaml = new Yaml(); private final Yaml yaml = new Yaml();
private final EventBus eventBus = new EventBus(); private final EventBus eventBus = new EventBus();
private final Map<String, Plugin> plugins = new HashMap<>(); private final Map<String, Plugin> plugins = new HashMap<>();
@@ -37,9 +43,10 @@ public class PluginManager
/** /**
* Register a command so that it may be executed. * Register a command so that it may be executed.
* *
* @param plugin the plugin owning this command
* @param command the command to register * @param command the command to register
*/ */
public void registerCommand(Command command) public void registerCommand(Plugin plugin, Command command)
{ {
commandMap.put( command.getName().toLowerCase(), command ); commandMap.put( command.getName().toLowerCase(), command );
for ( String alias : command.getAliases() ) for ( String alias : command.getAliases() )
@@ -120,21 +127,87 @@ public class PluginManager
*/ */
public void enablePlugins() public void enablePlugins()
{ {
Map<Plugin, Boolean> pluginStatuses = new HashMap<>();
for ( Map.Entry<String, Plugin> entry : plugins.entrySet() ) for ( Map.Entry<String, Plugin> entry : plugins.entrySet() )
{ {
Plugin plugin = entry.getValue(); Plugin plugin = entry.getValue();
if ( !this.enablePlugin( pluginStatuses, new Stack<Plugin>(), plugin ) )
{
ProxyServer.getInstance().getLogger().warning( "Failed to enable " + entry.getKey() );
}
}
}
private boolean enablePlugin(Map<Plugin, Boolean> pluginStatuses, Stack<Plugin> dependStack, Plugin plugin)
{
if ( pluginStatuses.containsKey( plugin ) )
{
return pluginStatuses.get( plugin );
}
// success status
boolean status = true;
// try to load dependencies first
for ( String dependName : plugin.getDescription().getDepends() )
{
Plugin depend = this.plugins.get( dependName );
Boolean dependStatus = depend != null ? pluginStatuses.get( depend ) : Boolean.FALSE;
if ( dependStatus == null )
{
if ( dependStack.contains( depend ) )
{
StringBuilder dependencyGraph = new StringBuilder();
for ( Plugin element : dependStack )
{
dependencyGraph.append( element.getDescription().getName() ).append( " -> " );
}
dependencyGraph.append( plugin.getDescription().getName() ).append( " -> " ).append( dependName );
ProxyServer.getInstance().getLogger().log( Level.WARNING, "Circular dependency detected: " + dependencyGraph );
status = false;
} else
{
dependStack.push( plugin );
dependStatus = this.enablePlugin( pluginStatuses, dependStack, depend );
dependStack.pop();
}
}
if ( dependStatus == Boolean.FALSE )
{
ProxyServer.getInstance().getLogger().log( Level.WARNING, "{0} (required by {1}) is unavailable", new Object[]
{
depend.getDescription().getName(), plugin.getDescription().getName()
} );
status = false;
}
if ( !status )
{
break;
}
}
// do actual loading
if ( status )
{
try try
{ {
plugin.onEnable(); plugin.onEnable();
ProxyServer.getInstance().getLogger().log( Level.INFO, "Enabled plugin {0} version {1} by {2}", new Object[] ProxyServer.getInstance().getLogger().log( Level.INFO, "Enabled plugin {0} version {1} by {2}", new Object[]
{ {
entry.getKey(), plugin.getDescription().getVersion(), plugin.getDescription().getAuthor() plugin.getDescription().getName(), plugin.getDescription().getVersion(), plugin.getDescription().getAuthor()
} ); } );
} catch ( Exception ex ) } catch ( Throwable t )
{ {
ProxyServer.getInstance().getLogger().log( Level.WARNING, "Exception encountered when loading plugin: " + entry.getKey(), ex ); ProxyServer.getInstance().getLogger().log( Level.WARNING, "Exception encountered when loading plugin: " + plugin.getDescription().getName(), t );
status = false;
} }
} }
pluginStatuses.put( plugin, status );
return status;
} }
/** /**
@@ -165,7 +238,7 @@ public class PluginManager
Class<?> main = loader.loadClass( desc.getMain() ); Class<?> main = loader.loadClass( desc.getMain() );
Plugin plugin = (Plugin) main.getDeclaredConstructor().newInstance(); Plugin plugin = (Plugin) main.getDeclaredConstructor().newInstance();
plugin.init( desc ); plugin.init( proxy, desc, file );
plugins.put( desc.getName(), plugin ); plugins.put( desc.getName(), plugin );
plugin.onLoad(); plugin.onLoad();
ProxyServer.getInstance().getLogger().log( Level.INFO, "Loaded plugin {0} version {1} by {2}", new Object[] ProxyServer.getInstance().getLogger().log( Level.INFO, "Loaded plugin {0} version {1} by {2}", new Object[]
@@ -211,7 +284,20 @@ public class PluginManager
*/ */
public <T extends Event> T callEvent(T event) public <T extends Event> T callEvent(T event)
{ {
Preconditions.checkNotNull( event, "event" );
long start = System.nanoTime();
eventBus.post( event ); eventBus.post( event );
event.postCall();
long elapsed = start - System.nanoTime();
if ( elapsed > 250000 )
{
ProxyServer.getInstance().getLogger().log( Level.WARNING, "Event {0} took more {1}ns to process!", new Object[]
{
event, elapsed
} );
}
return event; return event;
} }
@@ -220,9 +306,10 @@ public class PluginManager
* 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 Subscribe} annotation.
* *
* @param plugin the owning plugin
* @param listener the listener to register events for * @param listener the listener to register events for
*/ */
public void registerListener(Listener listener) public void registerListener(Plugin plugin, Listener listener)
{ {
eventBus.register( listener ); eventBus.register( listener );
} }

View File

@@ -0,0 +1,41 @@
package net.md_5.bungee.api.scheduler;
import java.util.concurrent.TimeUnit;
import net.md_5.bungee.api.plugin.Plugin;
/**
* Represents a task scheduled for execution by the {@link TaskScheduler}.
*/
public interface ScheduledTask
{
/**
* Gets the unique ID of this task.
*
* @return this tasks ID
*/
int getId();
/**
* Return the plugin which scheduled this task for execution.
*
* @return the owning plugin
*/
Plugin getOwner();
/**
* Get the actual method which will be executed by this task.
*
* @return the {@link Runnable} behind this task
*/
Runnable getTask();
/**
* Get the delay in the specified unit before this task will next be
* executed.
*
* @param unit the unit to get the delay in
* @return the time before the next execution of this task
*/
long getDelay(TimeUnit unit);
}

View File

@@ -0,0 +1,74 @@
package net.md_5.bungee.api.scheduler;
import java.util.concurrent.TimeUnit;
import net.md_5.bungee.api.plugin.Plugin;
/**
* This interface represents a scheduler which may be used to queue, delay and
* execute tasks in an asynchronous fashion.
*/
public interface TaskScheduler
{
/**
* Cancel a task to prevent it from executing, or if its a repeating task,
* prevent its further execution.
*
* @param id the id of the task to cancel
*/
void cancel(int id);
/**
* Cancel a task to prevent it from executing, or if its a repeating task,
* prevent its further execution.
*
* @param task the task to cancel
*/
void cancel(ScheduledTask task);
/**
* Cancel all tasks owned by this plugin, this preventing them from being
* executed hereon in.
*
* @param plugin the plugin owning the tasks to be cancelled
* @return the number of tasks cancelled by this method
*/
int cancel(Plugin plugin);
/**
* Schedule a task to be executed asynchronously. The task will commence
* running as soon as this method returns.
*
* @param owner the plugin owning this task
* @param task the task to run
* @return the scheduled task
*/
ScheduledTask runAsync(Plugin owner, Runnable task);
/**
* Schedules a task to be executed asynchronously after the specified delay
* is up.
*
* @param owner the plugin owning this task
* @param task the task to run
* @param delay the delay before this task will be executed
* @param unit the unit in which the delay will be measured
* @return the scheduled task
*/
ScheduledTask schedule(Plugin owner, Runnable task, long delay, TimeUnit unit);
/**
* Schedules a task to be executed asynchronously after the specified delay
* is up. The scheduled task will continue running at the specified
* interval. The interval will not begin to count down until the last task
* invocation is complete.
*
* @param owner the plugin owning this task
* @param task the task to run
* @param delay the delay in milliseconds before this task will be executed
* @param period the interval before subsequent executions of this task
* @param unit the unit in which the delay and period will be measured
* @return the scheduled task
*/
ScheduledTask schedule(Plugin owner, Runnable task, long delay, long period, TimeUnit unit);
}

View File

@@ -0,0 +1,20 @@
package net.md_5.bungee.api.scoreboard;
import lombok.Data;
/**
* Represents an objective entry.
*/
@Data
public class Objective
{
/**
* Name of the objective.
*/
private final String name;
/**
* Value of the objective.
*/
private final String value; // displayName
}

View File

@@ -0,0 +1,10 @@
package net.md_5.bungee.api.scoreboard;
/**
* Represents locations for a scoreboard to be displayed.
*/
public enum Position
{
LIST, SIDEBAR, BELOW;
}

View File

@@ -0,0 +1,24 @@
package net.md_5.bungee.api.scoreboard;
import lombok.Data;
/**
* Represents a scoreboard score entry.
*/
@Data
public class Score
{
/**
* Name to be displayed in the list.
*/
private final String itemName; // Player
/**
* Unique name of the score.
*/
private final String scoreName; // Score
/**
* Value of the score.
*/
private final int value;
}

View File

@@ -0,0 +1,101 @@
package net.md_5.bungee.api.scoreboard;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class Scoreboard
{
/**
* Unique name for this scoreboard.
*/
private String name;
/**
* Position of this scoreboard.
*/
private Position position;
/**
* Objectives for this scoreboard.
*/
private final Map<String, Objective> objectives = new HashMap<>();
/**
* Scores for this scoreboard.
*/
private final Map<String, Score> scores = new HashMap<>();
/**
* Teams on this board.
*/
private final Map<String, Team> teams = new HashMap<>();
public Collection<Objective> getObjectives()
{
return Collections.unmodifiableCollection( objectives.values() );
}
public Collection<Score> getScores()
{
return Collections.unmodifiableCollection( scores.values() );
}
public Collection<Team> getTeams()
{
return Collections.unmodifiableCollection( teams.values() );
}
public void addObjective(Objective objective)
{
Preconditions.checkNotNull( objective, "objective" );
Preconditions.checkArgument( !objectives.containsKey( objective.getName() ), "Objective %s already exists in this scoreboard", objective.getName() );
objectives.put( objective.getName(), objective );
}
public void addScore(Score score)
{
Preconditions.checkNotNull( score, "score" );
Preconditions.checkArgument( !scores.containsKey( score.getItemName() ), "Score %s already exists in this scoreboard", score.getItemName() );
scores.put( score.getItemName(), score );
}
public void addTeam(Team team)
{
Preconditions.checkNotNull( team, "team" );
Preconditions.checkArgument( !teams.containsKey( team.getName() ), "Team %s already exists in this scoreboard", team.getName() );
teams.put( team.getName(), team );
}
public Team getTeam(String name)
{
return teams.get( name );
}
public void removeObjective(String objectiveName)
{
objectives.remove( objectiveName );
}
public void removeScore(String scoreName)
{
scores.remove( scoreName );
}
public void removeTeam(String teamName)
{
teams.remove( teamName );
}
public void clear()
{
name = null;
position = null;
objectives.clear();
scores.clear();
teams.clear();
}
}

View File

@@ -0,0 +1,34 @@
package net.md_5.bungee.api.scoreboard;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import lombok.Data;
@Data
public class Team
{
private final String name;
private String displayName;
private String prefix;
private String suffix;
private byte friendlyMode;
private Set<String> players = new HashSet<>();
public Collection<String> getPlayers()
{
return Collections.unmodifiableSet( players );
}
public void addPlayer(String name)
{
players.add( name );
}
public void removePlayer(String name)
{
players.remove( name );
}
}

View File

@@ -11,7 +11,7 @@
<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.5-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>BungeeCord</name> <name>BungeeCord</name>
@@ -57,15 +57,16 @@
</ciManagement> </ciManagement>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<build.number>unknown</build.number> <build.number>unknown</build.number>
<netty.version>4.0.0.CR1</netty.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>0.11.6</version> <version>0.11.8</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -92,7 +93,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version> <version>2.5.1</version>
<configuration> <configuration>
<source>1.7</source> <source>1.7</source>
<target>1.7</target> <target>1.7</target>

View File

@@ -6,15 +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.5-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.4.7-SNAPSHOT</version> <version>1.5-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>BungeeCord-Protocol</name> <name>BungeeCord-Protocol</name>
<description>Minimal implementation of the Minecraft protocol for use in BungeeCord</description> <description>Minimal implementation of the Minecraft protocol for use in BungeeCord</description>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>${netty.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project> </project>

View File

@@ -1,19 +1,18 @@
package net.md_5.mendax; package net.md_5.bungee.protocol;
import static net.md_5.mendax.PacketDefinitions.OpCode.*; import static net.md_5.bungee.protocol.PacketDefinitions.OpCode.*;
public class PacketDefinitions public class PacketDefinitions
{ {
private static final int MAX_PACKET = 256; public static final OpCode[][] opCodes = new OpCode[ 512 ][];
public static final OpCode[][] opCodes = new OpCode[ MAX_PACKET * 2 ][];
public static final int VANILLA_PROTOCOL = 0; public static final int VANILLA_PROTOCOL = 0;
public static final int FORGE_PROTOCOL = MAX_PACKET; public static final int FORGE_PROTOCOL = 256;
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, SCORE, SHORT, SHORT_BYTE, SHORT_ITEM, STRING, TEAM, USHORT_BYTE
} }
static static
@@ -214,6 +213,10 @@ public class PacketDefinitions
{ {
STRING, INT, INT, INT, FLOAT, BYTE STRING, INT, INT, INT, FLOAT, BYTE
}; };
opCodes[0x3F] = new OpCode[]
{
STRING, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, INT
};
opCodes[0x46] = new OpCode[] opCodes[0x46] = new OpCode[]
{ {
BYTE, BYTE BYTE, BYTE
@@ -224,7 +227,7 @@ public class PacketDefinitions
}; };
opCodes[0x64] = new OpCode[] opCodes[0x64] = new OpCode[]
{ {
BYTE, BYTE, STRING, BYTE BYTE, BYTE, STRING, BYTE, BOOLEAN
}; };
opCodes[0x65] = new OpCode[] opCodes[0x65] = new OpCode[]
{ {
@@ -298,6 +301,22 @@ public class PacketDefinitions
{ {
BYTE BYTE
}; };
opCodes[0xCE] = new OpCode[]
{
STRING, STRING, BYTE
};
opCodes[0xCF] = new OpCode[]
{
SCORE
};
opCodes[0xD0] = new OpCode[]
{
BYTE, STRING
};
opCodes[0xD1] = new OpCode[]
{
TEAM
};
opCodes[0xFA] = new OpCode[] opCodes[0xFA] = new OpCode[]
{ {
STRING, SHORT_BYTE STRING, SHORT_BYTE
@@ -312,7 +331,8 @@ public class PacketDefinitions
}; };
opCodes[0xFE] = new OpCode[] opCodes[0xFE] = new OpCode[]
{ {
}; // Should be byte, screw you too bitchy server admins! BYTE
};
opCodes[0xFF] = new OpCode[] opCodes[0xFF] = new OpCode[]
{ {
STRING STRING

View File

@@ -1,17 +1,17 @@
package net.md_5.mendax.datainput; package net.md_5.bungee.protocol.netty;
import java.io.DataInput; import io.netty.buffer.ByteBuf;
import java.io.IOException; import java.io.IOException;
public class BulkChunk extends Instruction public class BulkChunk extends Instruction
{ {
@Override @Override
void read(DataInput in, byte[] buffer) throws IOException void read(ByteBuf in) throws IOException
{ {
short count = in.readShort(); short count = in.readShort();
int size = in.readInt(); int size = in.readInt();
in.readBoolean(); in.readBoolean();
skip( in, buffer, size + count * 12 ); in.skipBytes( size + count * 12 );
} }
} }

View File

@@ -1,6 +1,6 @@
package net.md_5.mendax.datainput; package net.md_5.bungee.protocol.netty;
import java.io.DataInput; import io.netty.buffer.ByteBuf;
import java.io.IOException; import java.io.IOException;
class ByteHeader extends Instruction class ByteHeader extends Instruction
@@ -14,12 +14,12 @@ class ByteHeader extends Instruction
} }
@Override @Override
void read(DataInput in, byte[] buffer) throws IOException void read(ByteBuf in) throws IOException
{ {
byte size = in.readByte(); byte size = in.readByte();
for ( byte b = 0; b < size; b++ ) for ( byte b = 0; b < size; b++ )
{ {
child.read( in, buffer ); child.read( in );
} }
} }
} }

View File

@@ -1,6 +1,6 @@
package net.md_5.mendax.datainput; package net.md_5.bungee.protocol.netty;
import java.io.DataInput; import io.netty.buffer.ByteBuf;
import java.io.IOException; import java.io.IOException;
abstract class Instruction abstract class Instruction
@@ -19,18 +19,17 @@ abstract class Instruction
static final Instruction LONG = new Jump( 8 ); static final Instruction LONG = new Jump( 8 );
static final Instruction METADATA = new MetaData(); static final Instruction METADATA = new MetaData();
static final Instruction OPTIONAL_MOTION = new OptionalMotion(); static final Instruction OPTIONAL_MOTION = new OptionalMotion();
static final Instruction SCORE = new Score();
static final Instruction SHORT = new Jump( 2 ); static final Instruction SHORT = new Jump( 2 );
static final Instruction SHORT_BYTE = new ShortHeader( BYTE ); static final Instruction SHORT_BYTE = new ShortHeader( BYTE );
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 TEAM = new Team();
static final Instruction USHORT_BYTE = new UnsignedShortByte(); static final Instruction USHORT_BYTE = new UnsignedShortByte();
// 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
static final Instruction STRING_ARRAY = new ShortHeader( STRING );
abstract void read(DataInput in, byte[] buffer) throws IOException; abstract void read(ByteBuf in) throws IOException;
final void skip(DataInput in, byte[] buffer, int len) throws IOException
{
in.readFully( buffer, 0, len );
}
} }

View File

@@ -1,6 +1,6 @@
package net.md_5.mendax.datainput; package net.md_5.bungee.protocol.netty;
import java.io.DataInput; import io.netty.buffer.ByteBuf;
import java.io.IOException; import java.io.IOException;
class IntHeader extends Instruction class IntHeader extends Instruction
@@ -14,12 +14,12 @@ class IntHeader extends Instruction
} }
@Override @Override
void read(DataInput in, byte[] buffer) throws IOException void read(ByteBuf in) throws IOException
{ {
int size = in.readInt(); int size = in.readInt();
for ( int i = 0; i < size; i++ ) for ( int i = 0; i < size; i++ )
{ {
child.read( in, buffer ); child.read( in );
} }
} }
} }

View File

@@ -0,0 +1,19 @@
package net.md_5.bungee.protocol.netty;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
class Item extends Instruction
{
@Override
void read(ByteBuf in) throws IOException
{
short type = in.readShort();
if ( type >= 0 )
{
in.skipBytes( 3 );
SHORT_BYTE.read( in );
}
}
}

View File

@@ -1,6 +1,6 @@
package net.md_5.mendax.datainput; package net.md_5.bungee.protocol.netty;
import java.io.DataInput; import io.netty.buffer.ByteBuf;
import java.io.IOException; import java.io.IOException;
class Jump extends Instruction class Jump extends Instruction
@@ -18,8 +18,8 @@ class Jump extends Instruction
} }
@Override @Override
void read(DataInput in, byte[] buffer) throws IOException void read(ByteBuf in) throws IOException
{ {
skip( in, buffer, len ); in.skipBytes( len );
} }
} }

View File

@@ -1,13 +1,13 @@
package net.md_5.mendax.datainput; package net.md_5.bungee.protocol.netty;
import java.io.DataInput; import io.netty.buffer.ByteBuf;
import java.io.IOException; import java.io.IOException;
class MetaData extends Instruction class MetaData extends Instruction
{ {
@Override @Override
void read(DataInput in, byte[] buffer) throws IOException void read(ByteBuf in) throws IOException
{ {
int x = in.readUnsignedByte(); int x = in.readUnsignedByte();
while ( x != 127 ) while ( x != 127 )
@@ -16,25 +16,25 @@ class MetaData extends Instruction
switch ( type ) switch ( type )
{ {
case 0: case 0:
BYTE.read( in, buffer ); BYTE.read( in );
break; break;
case 1: case 1:
SHORT.read( in, buffer ); SHORT.read( in );
break; break;
case 2: case 2:
INT.read( in, buffer ); INT.read( in );
break; break;
case 3: case 3:
FLOAT.read( in, buffer ); FLOAT.read( in );
break; break;
case 4: case 4:
STRING.read( in, buffer ); STRING.read( in );
break; break;
case 5: case 5:
ITEM.read( in, buffer ); ITEM.read( in );
break; break;
case 6: case 6:
skip( in, buffer, 12 ); // int, int, int in.skipBytes( 12 ); // int, int, int
break; break;
default: default:
throw new IllegalArgumentException( "Unknown metadata type " + type ); throw new IllegalArgumentException( "Unknown metadata type " + type );

View File

@@ -0,0 +1,18 @@
package net.md_5.bungee.protocol.netty;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
class OptionalMotion extends Instruction
{
@Override
void read(ByteBuf in) throws IOException
{
int data = in.readInt();
if ( data > 0 )
{
in.skipBytes( 6 );
}
}
}

View File

@@ -1,16 +1,16 @@
package net.md_5.mendax.datainput; package net.md_5.bungee.protocol.netty;
import java.io.DataInput; import io.netty.buffer.ByteBuf;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import net.md_5.mendax.PacketDefinitions; import net.md_5.bungee.protocol.PacketDefinitions;
import net.md_5.mendax.PacketDefinitions.OpCode; import net.md_5.bungee.protocol.PacketDefinitions.OpCode;
public class DataInputPacketReader public class PacketReader
{ {
private static final Instruction[][] instructions = new Instruction[ 256 ][]; private static final Instruction[][] instructions = new Instruction[ PacketDefinitions.opCodes.length ][];
static static
{ {
@@ -59,7 +59,7 @@ public class DataInputPacketReader
} }
} }
private static void readPacket(int packetId, DataInput in, byte[] buffer, int protocol) throws IOException private static void readPacket(int packetId, ByteBuf in, int protocol) throws IOException
{ {
Instruction[] packetDef = null; Instruction[] packetDef = null;
if ( packetId + protocol < instructions.length ) if ( packetId + protocol < instructions.length )
@@ -74,20 +74,20 @@ public class DataInputPacketReader
throw new IOException( "Unknown packet id " + packetId ); throw new IOException( "Unknown packet id " + packetId );
} else } else
{ {
readPacket( packetId, in, buffer, PacketDefinitions.VANILLA_PROTOCOL ); readPacket( packetId, in, PacketDefinitions.VANILLA_PROTOCOL );
return; return;
} }
} }
for ( Instruction instruction : packetDef ) for ( Instruction instruction : packetDef )
{ {
instruction.read( in, buffer ); instruction.read( in );
} }
} }
public static void readPacket(DataInput in, byte[] buffer, int protocol) throws IOException public static void readPacket(ByteBuf in, int protocol) throws IOException
{ {
int packetId = in.readUnsignedByte(); int packetId = in.readUnsignedByte();
readPacket( packetId, in, buffer, protocol ); readPacket( packetId, in, protocol );
} }
} }

View File

@@ -0,0 +1,19 @@
package net.md_5.bungee.protocol.netty;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
public class Score extends Instruction
{
@Override
void read(ByteBuf in) throws IOException
{
STRING.read( in );
if ( in.readByte() == 0 )
{
STRING.read( in );
INT.read( in );
}
}
}

View File

@@ -1,6 +1,6 @@
package net.md_5.mendax.datainput; package net.md_5.bungee.protocol.netty;
import java.io.DataInput; import io.netty.buffer.ByteBuf;
import java.io.IOException; import java.io.IOException;
class ShortHeader extends Instruction class ShortHeader extends Instruction
@@ -14,12 +14,12 @@ class ShortHeader extends Instruction
} }
@Override @Override
void read(DataInput in, byte[] buffer) throws IOException void read(ByteBuf in) throws IOException
{ {
short size = in.readShort(); short size = in.readShort();
for ( short s = 0; s < size; s++ ) for ( short s = 0; s < size; s++ )
{ {
child.read( in, buffer ); child.read( in );
} }
} }
} }

View File

@@ -0,0 +1,26 @@
package net.md_5.bungee.protocol.netty;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
class Team extends Instruction
{
@Override
void read(ByteBuf in) throws IOException
{
STRING.read( in );
byte mode = in.readByte();
if ( mode == 0 || mode == 2 )
{
STRING.read( in );
STRING.read( in );
STRING.read( in );
BYTE.read( in );
}
if ( mode == 0 || mode == 3 || mode == 4 )
{
STRING_ARRAY.read( in );
}
}
}

View File

@@ -0,0 +1,15 @@
package net.md_5.bungee.protocol.netty;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
class UnsignedShortByte extends Instruction
{
@Override
void read(ByteBuf in) throws IOException
{
int size = in.readUnsignedShort();
in.skipBytes( size );
}
}

View File

@@ -1,19 +0,0 @@
package net.md_5.mendax.datainput;
import java.io.DataInput;
import java.io.IOException;
class Item extends Instruction
{
@Override
void read(DataInput in, byte[] buffer) throws IOException
{
short type = in.readShort();
if ( type >= 0 )
{
skip( in, buffer, 3 );
SHORT_BYTE.read( in, buffer );
}
}
}

View File

@@ -1,18 +0,0 @@
package net.md_5.mendax.datainput;
import java.io.DataInput;
import java.io.IOException;
public class OptionalMotion extends Instruction
{
@Override
void read(DataInput in, byte[] buffer) throws IOException
{
int data = in.readInt();
if ( data > 0 )
{
skip( in, buffer, 6 );
}
}
}

View File

@@ -1,15 +0,0 @@
package net.md_5.mendax.datainput;
import java.io.DataInput;
import java.io.IOException;
public class UnsignedShortByte extends Instruction
{
@Override
void read(DataInput in, byte[] buffer) throws IOException
{
int size = in.readUnsignedShort();
skip( in, buffer, size );
}
}

View File

@@ -6,39 +6,68 @@
<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.5-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.4.7-SNAPSHOT</version> <version>1.5-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>
<dependencies> <dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
<version>${netty.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>${netty.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>
<version>${project.version}</version> <version>${project.version}</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId> <artifactId>bungeecord-api</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.sf.trove4j</groupId>
<artifactId>trove4j</artifactId>
<version>3.0.3</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>5.1.22</version> <version>5.1.24</version>
<scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<finalName>BungeeCord</finalName> <finalName>BungeeCord</finalName>
<plugins> <plugins>
<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> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
@@ -75,7 +104,6 @@
</excludes> </excludes>
</filter> </filter>
</filters> </filters>
<finalName>${project.build.finalName}-shaded</finalName>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@@ -1,29 +1,40 @@
package net.md_5.bungee; package net.md_5.bungee;
import net.md_5.bungee.scheduler.BungeeScheduler;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.ning.http.client.AsyncHttpClient;
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.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import net.md_5.bungee.config.Configuration; import net.md_5.bungee.config.Configuration;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.util.Calendar;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.Synchronized; import lombok.Synchronized;
import static net.md_5.bungee.Logger.$; 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.ReconnectHandler; import net.md_5.bungee.api.ReconnectHandler;
import net.md_5.bungee.api.TabListHandler; import net.md_5.bungee.api.TabListHandler;
@@ -34,10 +45,13 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.connection.Server;
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.command.*; import net.md_5.bungee.command.*;
import net.md_5.bungee.config.YamlConfig; import net.md_5.bungee.config.YamlConfig;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.packet.DefinedPacket; import net.md_5.bungee.packet.DefinedPacket;
import net.md_5.bungee.packet.PacketFAPluginMessage; import net.md_5.bungee.packet.PacketFAPluginMessage;
import net.md_5.bungee.scheduler.BungeeThreadPool;
/** /**
* Main BungeeCord proxy class. * Main BungeeCord proxy class.
@@ -48,11 +62,11 @@ public class BungeeCord extends ProxyServer
/** /**
* Server protocol version. * Server protocol version.
*/ */
public static final byte PROTOCOL_VERSION = 51; public static final byte PROTOCOL_VERSION = 60;
/** /**
* Server game version. * Server game version.
*/ */
public static final String GAME_VERSION = "1.4.6"; public static final String GAME_VERSION = "1.5";
/** /**
* Current operation state. * Current operation state.
*/ */
@@ -62,9 +76,10 @@ public class BungeeCord extends ProxyServer
*/ */
public final Configuration config = new Configuration(); public final Configuration config = new Configuration();
/** /**
* Thread pool. * Thread pools.
*/ */
public final ExecutorService threadPool = Executors.newCachedThreadPool(); public final ScheduledThreadPoolExecutor executors = new BungeeThreadPool( new ThreadFactoryBuilder().setNameFormat( "Bungee Pool Thread #%1$d" ).build() );
public final MultithreadEventLoopGroup eventLoops = new NioEventLoopGroup( 0, new ThreadFactoryBuilder().setNameFormat( "Netty IO Thread #%1$d" ).build() );
/** /**
* locations.yml save thread. * locations.yml save thread.
*/ */
@@ -72,7 +87,7 @@ public class BungeeCord extends ProxyServer
/** /**
* Server socket listener. * Server socket listener.
*/ */
private Collection<ListenThread> listeners = new HashSet<>(); private Collection<Channel> listeners = new HashSet<>();
/** /**
* Fully qualified connections. * Fully qualified connections.
*/ */
@@ -87,7 +102,7 @@ public class BungeeCord extends ProxyServer
* Plugin manager. * Plugin manager.
*/ */
@Getter @Getter
public final PluginManager pluginManager = new PluginManager(); public final PluginManager pluginManager = new PluginManager( this );
@Getter @Getter
@Setter @Setter
private ReconnectHandler reconnectHandler; private ReconnectHandler reconnectHandler;
@@ -95,17 +110,28 @@ public class BungeeCord extends ProxyServer
@Setter @Setter
private ConfigurationAdapter configurationAdapter = new YamlConfig(); private ConfigurationAdapter configurationAdapter = new YamlConfig();
private final Collection<String> pluginChannels = new HashSet<>(); private final Collection<String> pluginChannels = new HashSet<>();
@Getter
private final File pluginsFolder = new File( "plugins" );
@Getter
private final TaskScheduler 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() ) );
{ {
getPluginManager().registerCommand( new CommandReload() ); // TODO: Proper fallback when we interface the manager
getPluginManager().registerCommand( new CommandEnd() ); getPluginManager().registerCommand( null, new CommandReload() );
getPluginManager().registerCommand( new CommandList() ); getPluginManager().registerCommand( null, new CommandEnd() );
getPluginManager().registerCommand( new CommandServer() ); getPluginManager().registerCommand( null, new CommandList() );
getPluginManager().registerCommand( new CommandIP() ); getPluginManager().registerCommand( null, new CommandServer() );
getPluginManager().registerCommand( new CommandAlert() ); getPluginManager().registerCommand( null, new CommandIP() );
getPluginManager().registerCommand( new CommandBungee() ); getPluginManager().registerCommand( null, new CommandAlert() );
getPluginManager().registerCommand( new CommandPerms() ); getPluginManager().registerCommand( null, new CommandBungee() );
getPluginManager().registerCommand( null, new CommandPerms() );
getPluginManager().registerCommand( null, new CommandSend() );
registerChannel( "BungeeCord" ); registerChannel( "BungeeCord" );
} }
@@ -119,13 +145,24 @@ public class BungeeCord extends ProxyServer
* Starts a new instance of BungeeCord. * Starts a new instance of BungeeCord.
* *
* @param args command line arguments, currently none are used * @param args command line arguments, currently none are used
* @throws IOException when the server cannot be started * @throws Exception when the server cannot be started
*/ */
public static void main(String[] args) throws IOException public static void main(String[] args) throws Exception
{ {
Calendar deadline = Calendar.getInstance();
deadline.set( 2013, 5, 26 ); // 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(); BungeeCord bungee = new BungeeCord();
ProxyServer.setInstance( bungee ); ProxyServer.setInstance( bungee );
$().info( "Enabled BungeeCord version " + bungee.getVersion() ); bungee.getLogger().info( "Enabled BungeeCord version " + bungee.getVersion() );
bungee.start(); bungee.start();
BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ); BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
@@ -147,13 +184,13 @@ public class BungeeCord extends ProxyServer
* 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.
* *
* @throws IOException * @throws Exception
*/ */
public void start() throws IOException @Override
public void start() throws Exception
{ {
File plugins = new File( "plugins" ); pluginsFolder.mkdir();
plugins.mkdir(); pluginManager.loadPlugins( pluginsFolder );
pluginManager.loadPlugins( plugins );
config.load(); config.load();
if ( reconnectHandler == null ) if ( reconnectHandler == null )
{ {
@@ -179,34 +216,43 @@ public class BungeeCord extends ProxyServer
public void startListeners() public void startListeners()
{ {
for ( ListenerInfo info : config.getListeners() ) for ( final ListenerInfo info : config.getListeners() )
{ {
try new ServerBootstrap()
.channel( NioServerSocketChannel.class )
.childAttr( PipelineUtils.LISTENER, info )
.childHandler( PipelineUtils.SERVER_CHILD )
.group( eventLoops )
.localAddress( info.getHost() )
.bind().addListener( new ChannelFutureListener()
{ {
ListenThread listener = new ListenThread( info ); @Override
listener.start(); public void operationComplete(ChannelFuture future) throws Exception
listeners.add( listener ); {
$().info( "Listening on " + info.getHost() ); if ( future.isSuccess() )
} catch ( IOException ex ) {
{ listeners.add( future.channel() );
$().log( Level.SEVERE, "Could not start listener " + info, ex ); getLogger().info( "Listening on " + info.getHost() );
} } else
{
getLogger().log( Level.WARNING, "Could not bind to host " + info.getHost(), future.cause() );
}
}
} );
} }
} }
public void stopListeners() public void stopListeners()
{ {
for ( ListenThread listener : listeners ) for ( Channel listener : listeners )
{ {
$().log( Level.INFO, "Closing listen thread {0}", listener.socket ); getLogger().log( Level.INFO, "Closing listener {0}", listener );
try try
{ {
listener.interrupt(); listener.close().syncUninterruptibly();
listener.socket.close(); } catch ( ChannelException ex )
listener.join();
} catch ( InterruptedException | IOException ex )
{ {
$().severe( "Could not close listen thread" ); getLogger().severe( "Could not close listen thread" );
} }
} }
listeners.clear(); listeners.clear();
@@ -217,44 +263,37 @@ public class BungeeCord extends ProxyServer
{ {
this.isRunning = false; this.isRunning = false;
stopListeners(); httpClient.close();
$().info( "Closing pending connections" ); executors.shutdown();
threadPool.shutdown();
$().info( "Disconnecting " + connections.size() + " connections" ); stopListeners();
getLogger().info( "Closing pending connections" );
getLogger().info( "Disconnecting " + connections.size() + " connections" );
for ( UserConnection user : connections.values() ) for ( UserConnection user : connections.values() )
{ {
user.disconnect( "Proxy restarting, brb." ); user.disconnect( "Proxy restarting, brb." );
} }
$().info( "Saving reconnect locations" ); getLogger().info( "Closing IO threads" );
eventLoops.shutdown();
getLogger().info( "Saving reconnect locations" );
reconnectHandler.save(); reconnectHandler.save();
saveThread.cancel(); saveThread.cancel();
$().info( "Disabling plugins" ); // TODO: Fix this shit
getLogger().info( "Disabling plugins" );
for ( Plugin plugin : pluginManager.getPlugins() ) for ( Plugin plugin : pluginManager.getPlugins() )
{ {
plugin.onDisable(); plugin.onDisable();
getScheduler().cancel( plugin );
} }
$().info( "Thank you and goodbye" ); getLogger().info( "Thank you and goodbye" );
System.exit( 0 ); System.exit( 0 );
} }
/**
* Miscellaneous method to set options on a socket based on those in the
* configuration.
*
* @param socket to set the options on
* @throws IOException when the underlying set methods thrown an exception
*/
public void setSocketOptions(Socket socket) throws IOException
{
socket.setSoTimeout( config.getTimeout() );
socket.setTrafficClass( 0x18 );
socket.setTcpNoDelay( true );
}
/** /**
* Broadcasts a packet to all clients that is connected to this instance. * Broadcasts a packet to all clients that is connected to this instance.
* *
@@ -264,7 +303,7 @@ public class BungeeCord extends ProxyServer
{ {
for ( UserConnection con : connections.values() ) for ( UserConnection con : connections.values() )
{ {
con.packetQueue.add( packet ); con.sendPacket( packet );
} }
} }
@@ -283,7 +322,7 @@ public class BungeeCord extends ProxyServer
@Override @Override
public Logger getLogger() public Logger getLogger()
{ {
return $(); return BungeeLogger.instance;
} }
@Override @Override
@@ -364,8 +403,14 @@ public class BungeeCord extends ProxyServer
} }
@Override @Override
public ServerInfo constructServerInfo(String name, InetSocketAddress address) public ServerInfo constructServerInfo(String name, InetSocketAddress address, boolean restricted)
{ {
return new BungeeServerInfo( name, address ); return new BungeeServerInfo( name, address, restricted );
}
@Override
public CommandSender getConsole()
{
return ConsoleCommandSender.getInstance();
} }
} }

View File

@@ -3,28 +3,28 @@ package net.md_5.bungee;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.text.MessageFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.logging.FileHandler; import java.util.logging.FileHandler;
import java.util.logging.Formatter; import java.util.logging.Formatter;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.LogRecord; import java.util.logging.LogRecord;
import java.util.logging.Logger;
/** /**
* Logger to handle formatting and storage of the proxy's logger. * Logger to handle formatting and storage of the proxy's logger.
*/ */
public class Logger extends java.util.logging.Logger public class BungeeLogger extends Logger
{ {
private static final Formatter formatter = new ConsoleLogFormatter(); private static final Formatter formatter = new ConsoleLogFormatter();
private static final Logger instance = new Logger(); static final BungeeLogger instance = new BungeeLogger();
public Logger() public BungeeLogger()
{ {
super( "RubberBand", null ); super( "BungeeCord", null );
try try
{ {
FileHandler handler = new FileHandler( "proxy.log", 1 << 14, 1, true ); FileHandler handler = new FileHandler( "proxy.log", 1 << 24, 8, true );
handler.setFormatter( formatter ); handler.setFormatter( formatter );
addHandler( handler ); addHandler( handler );
} catch ( IOException ex ) } catch ( IOException ex )
@@ -48,16 +48,6 @@ public class Logger extends java.util.logging.Logger
} }
} }
/**
* Gets the current logger instance.
*
* @return the current logger instance
*/
public static Logger $()
{
return instance;
}
public static class ConsoleLogFormatter extends Formatter public static class ConsoleLogFormatter extends Formatter
{ {

View File

@@ -1,31 +1,82 @@
package net.md_5.bungee; package net.md_5.bungee;
import java.io.DataOutputStream; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Synchronized;
import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.Callback;
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.ServerPing; import net.md_5.bungee.api.ServerPing;
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.Server; import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.connection.PingHandler;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.packet.DefinedPacket; import net.md_5.bungee.packet.DefinedPacket;
import net.md_5.bungee.packet.PacketFAPluginMessage; import net.md_5.bungee.packet.PacketFAPluginMessage;
import net.md_5.bungee.packet.PacketFFKick;
import net.md_5.bungee.packet.PacketStream;
import net.md_5.mendax.PacketDefinitions;
public class BungeeServerInfo extends ServerInfo @RequiredArgsConstructor
public class BungeeServerInfo implements ServerInfo
{ {
@Getter
private final String name;
@Getter
private final InetSocketAddress address;
private final Collection<ProxiedPlayer> players = new ArrayList<>();
@Getter
private final boolean restricted;
@Getter @Getter
private final Queue<DefinedPacket> packetQueue = new ConcurrentLinkedQueue<>(); private final Queue<DefinedPacket> packetQueue = new ConcurrentLinkedQueue<>();
public BungeeServerInfo(String name, InetSocketAddress address) @Synchronized("players")
public void addPlayer(ProxiedPlayer player)
{ {
super( name, address ); players.add( player );
}
@Synchronized("players")
public void removePlayer(ProxiedPlayer player)
{
players.remove( player );
}
@Synchronized("players")
@Override
public Collection<ProxiedPlayer> getPlayers()
{
return Collections.unmodifiableCollection( players );
}
@Override
public boolean canAccess(CommandSender player)
{
return !restricted || player.hasPermission( "bungeecord.server." + name );
}
@Override
public boolean equals(Object obj)
{
return ( obj instanceof ServerInfo ) && Objects.equals( getAddress(), ( (ServerInfo) obj ).getAddress() );
}
@Override
public int hashCode()
{
return address.hashCode();
} }
@Override @Override
@@ -44,31 +95,26 @@ public class BungeeServerInfo extends ServerInfo
@Override @Override
public void ping(final Callback<ServerPing> callback) public void ping(final Callback<ServerPing> callback)
{ {
new Thread() new Bootstrap()
.channel( NioSocketChannel.class )
.group( BungeeCord.getInstance().eventLoops )
.handler( PipelineUtils.BASE )
.option( ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000 ) // TODO: Configurable
.remoteAddress( getAddress() )
.connect()
.addListener( new ChannelFutureListener()
{ {
@Override @Override
public void run() public void operationComplete(ChannelFuture future) throws Exception
{ {
try ( Socket socket = new Socket(); ) if ( future.isSuccess() )
{ {
socket.connect( getAddress() ); future.channel().pipeline().get( HandlerBoss.class ).setHandler( new PingHandler( BungeeServerInfo.this, callback ) );
} else
DataOutputStream out = new DataOutputStream( socket.getOutputStream() );
out.write( 0xFE );
out.write( 0x01 );
PacketStream in = new PacketStream( socket.getInputStream(), PacketDefinitions.VANILLA_PROTOCOL );
PacketFFKick response = new PacketFFKick( in.readPacket() );
String[] split = response.message.split( "\00" );
ServerPing ping = new ServerPing( Byte.parseByte( split[1] ), split[2], split[3], Integer.parseInt( split[4] ), Integer.parseInt( split[5] ) );
callback.done( ping, null );
} catch ( Throwable t )
{ {
callback.done( null, t ); callback.done( null, future.cause() );
} }
} }
}.start(); } );
} }
} }

View File

@@ -1,17 +1,11 @@
package net.md_5.bungee; package net.md_5.bungee;
import java.io.BufferedReader; import java.security.GeneralSecurityException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLEncoder;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.Key; import java.security.Key;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Random; import java.util.Random;
@@ -32,7 +26,7 @@ public class EncryptionUtil
{ {
private static final Random random = new Random(); private static final Random random = new Random();
private static KeyPair keys; public static KeyPair keys;
public static PacketFDEncryptionRequest encryptRequest() throws NoSuchAlgorithmException public static PacketFDEncryptionRequest encryptRequest() throws NoSuchAlgorithmException
{ {
@@ -66,31 +60,7 @@ public class EncryptionUtil
return new SecretKeySpec( secret, "AES" ); return new SecretKeySpec( secret, "AES" );
} }
public static boolean isAuthenticated(String username, String connectionHash, SecretKey shared) throws NoSuchAlgorithmException, IOException public static Cipher getCipher(int opMode, Key shared) throws GeneralSecurityException
{
String encName = URLEncoder.encode( username, "UTF-8" );
MessageDigest sha = MessageDigest.getInstance( "SHA-1" );
for ( byte[] bit : new byte[][]
{
connectionHash.getBytes( "ISO_8859_1" ), shared.getEncoded(), keys.getPublic().getEncoded()
} )
{
sha.update( bit );
}
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 reply;
try ( BufferedReader in = new BufferedReader( new InputStreamReader( new URL( authURL ).openStream() ) ) )
{
reply = in.readLine();
}
return "YES".equals( reply );
}
public static Cipher getCipher(int opMode, Key shared) throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException
{ {
Cipher cip = Cipher.getInstance( "AES/CFB8/NoPadding" ); Cipher cip = Cipher.getInstance( "AES/CFB8/NoPadding" );
cip.init( opMode, shared, new IvParameterSpec( shared.getEncoded() ) ); cip.init( opMode, shared, new IvParameterSpec( shared.getEncoded() ) );

View File

@@ -40,7 +40,7 @@ public class EntityMap
}; };
entityIds[0x17] = new int[] entityIds[0x17] = new int[]
{ {
1, 20 1 //, 20
}; };
entityIds[0x18] = new int[] entityIds[0x18] = new int[]
{ {
@@ -115,7 +115,7 @@ public class EntityMap
public static void rewrite(byte[] packet, int oldId, int newId) public static void rewrite(byte[] packet, int oldId, int newId)
{ {
int packetId = Util.getId( packet ); int packetId = packet[0] & 0xFF;
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.length; pos += 4 )
@@ -147,6 +147,17 @@ public class EntityMap
} }
} }
} }
if ( packetId == 0x17 )
{
int type = packet[5] & 0xFF;
if ( type == 60 || type == 90 )
{
if ( readInt( packet, 20 ) == oldId )
{
setInt( packet, 20, newId );
}
}
}
} }
private static void setInt(byte[] buf, int pos, int i) private static void setInt(byte[] buf, int pos, int i)

View File

@@ -1,61 +0,0 @@
package net.md_5.bungee;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import static net.md_5.bungee.Logger.$;
import net.md_5.bungee.packet.PacketFFKick;
import net.md_5.bungee.packet.PacketStream;
/**
* Class to represent a Minecraft connection.
*/
@EqualsAndHashCode
@RequiredArgsConstructor
public class GenericConnection
{
protected final Socket socket;
protected final PacketStream stream;
@Getter
public String name;
@Getter
public String displayName;
/**
* Close the socket with the specified reason.
*
* @param reason to disconnect
*/
public void disconnect(String reason)
{
if ( socket.isClosed() )
{
return;
}
log( "disconnected with " + reason );
try
{
stream.write( new PacketFFKick( "[Proxy] " + reason ) );
} catch ( IOException ex )
{
} finally
{
try
{
socket.shutdownOutput();
socket.close();
} catch ( IOException ioe )
{
}
}
}
public void log(String message)
{
$().info( socket.getInetAddress() + ( ( name == null ) ? " " : " [" + name + "] " ) + message );
}
}

View File

@@ -1,232 +0,0 @@
package net.md_5.bungee;
import com.google.common.base.Preconditions;
import java.io.EOFException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import lombok.Getter;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.packet.DefinedPacket;
import net.md_5.bungee.packet.Packet1Login;
import net.md_5.bungee.packet.Packet2Handshake;
import net.md_5.bungee.packet.PacketCDClientStatus;
import net.md_5.bungee.packet.PacketFAPluginMessage;
import net.md_5.bungee.packet.PacketFCEncryptionResponse;
import net.md_5.bungee.packet.PacketFDEncryptionRequest;
import net.md_5.bungee.packet.PacketFEPing;
import net.md_5.bungee.packet.PacketFFKick;
import net.md_5.bungee.packet.PacketHandler;
import net.md_5.bungee.packet.PacketStream;
import net.md_5.mendax.PacketDefinitions;
public class InitialHandler extends PacketHandler implements Runnable, PendingConnection
{
private final Socket socket;
@Getter
private final ListenerInfo listener;
private PacketStream stream;
private Packet1Login forgeLogin;
private Packet2Handshake handshake;
private PacketFDEncryptionRequest request;
private List<PacketFAPluginMessage> loginMessages = new ArrayList<>();
private State thisState = State.HANDSHAKE;
private static final PacketFAPluginMessage forgeMods = new PacketFAPluginMessage( "FML", new byte[]
{
0, 0, 0, 0, 0, 2
} );
public InitialHandler(Socket socket, ListenerInfo info) throws IOException
{
this.socket = socket;
this.listener = info;
stream = new PacketStream( socket.getInputStream(), socket.getOutputStream(), PacketDefinitions.VANILLA_PROTOCOL );
}
private enum State
{
HANDSHAKE, ENCRYPT, LOGIN, FINISHED;
}
@Override
public void handle(Packet1Login login) throws Exception
{
Preconditions.checkState( thisState == State.LOGIN, "Not expecting FORGE LOGIN" );
Preconditions.checkState( forgeLogin == null, "Already received FORGE LOGIN" );
forgeLogin = login;
stream.setProtocol( PacketDefinitions.FORGE_PROTOCOL );
}
@Override
public void handle(PacketFAPluginMessage pluginMessage) throws Exception
{
loginMessages.add( pluginMessage );
}
@Override
public void handle(PacketFEPing ping) throws Exception
{
socket.setSoTimeout( 100 );
boolean newPing = false;
try
{
socket.getInputStream().read();
newPing = true;
} catch ( IOException ex )
{
}
ServerPing pingevent = new ServerPing( BungeeCord.PROTOCOL_VERSION, BungeeCord.GAME_VERSION,
listener.getMotd(), ProxyServer.getInstance().getPlayers().size(), listener.getMaxPlayers() );
pingevent = ProxyServer.getInstance().getPluginManager().callEvent( new ProxyPingEvent( this, pingevent ) ).getResponse();
String response = ( newPing ) ? ChatColor.COLOR_CHAR + "1"
+ "\00" + pingevent.getProtocolVersion()
+ "\00" + pingevent.getGameVersion()
+ "\00" + pingevent.getMotd()
+ "\00" + pingevent.getCurrentPlayers()
+ "\00" + pingevent.getMaxPlayers()
: pingevent.getMotd() + ChatColor.COLOR_CHAR + pingevent.getCurrentPlayers() + ChatColor.COLOR_CHAR + pingevent.getMaxPlayers();
disconnect( response );
}
@Override
public void handle(Packet2Handshake handshake) throws Exception
{
Preconditions.checkState( thisState == State.HANDSHAKE, "Not expecting HANDSHAKE" );
this.handshake = handshake;
stream.write( forgeMods );
stream.write( request = EncryptionUtil.encryptRequest() );
thisState = State.ENCRYPT;
}
@Override
public void handle(PacketFCEncryptionResponse encryptResponse) throws Exception
{
Preconditions.checkState( thisState == State.ENCRYPT, "Not expecting ENCRYPT" );
SecretKey shared = EncryptionUtil.getSecret( encryptResponse, request );
if ( BungeeCord.getInstance().config.isOnlineMode() && !EncryptionUtil.isAuthenticated( handshake.username, request.serverId, shared ) )
{
throw new KickException( "Not authenticated with minecraft.net" );
}
// Check for multiple connections
ProxiedPlayer old = ProxyServer.getInstance().getPlayer( handshake.username );
if ( old != null )
{
old.disconnect( "You are already connected to the server" );
}
// fire login event
LoginEvent event = new LoginEvent( this );
ProxyServer.getInstance().getPluginManager().callEvent( event );
if ( event.isCancelled() )
{
throw new KickException( event.getCancelReason() );
}
stream.write( new PacketFCEncryptionResponse() );
stream = new PacketStream( new CipherInputStream( socket.getInputStream(), EncryptionUtil.getCipher( Cipher.DECRYPT_MODE, shared ) ),
new CipherOutputStream( socket.getOutputStream(), EncryptionUtil.getCipher( Cipher.ENCRYPT_MODE, shared ) ), stream.getProtocol() );
thisState = State.LOGIN;
}
@Override
public void handle(PacketCDClientStatus clientStatus) throws Exception
{
Preconditions.checkState( thisState == State.LOGIN, "Not expecting LOGIN" );
UserConnection userCon = new UserConnection( socket, this, stream, handshake, forgeLogin, loginMessages );
ServerInfo server = ProxyServer.getInstance().getReconnectHandler().getServer( userCon );
userCon.connect( server, true );
thisState = State.FINISHED;
}
@Override
public void run()
{
try
{
while ( thisState != State.FINISHED )
{
byte[] buf = stream.readPacket();
DefinedPacket packet = DefinedPacket.packet( buf );
packet.handle( this );
}
} catch ( KickException ex )
{
disconnect( "[Proxy - Kicked] " + ex.getMessage() );
} catch ( EOFException ex )
{
} catch ( Exception ex )
{
disconnect( "[Proxy Error] " + Util.exception( ex ) );
ex.printStackTrace();
}
}
@Override
public void disconnect(String reason)
{
thisState = State.FINISHED;
try
{
stream.write( new PacketFFKick( reason ) );
} catch ( IOException ioe )
{
} finally
{
try
{
socket.shutdownOutput();
socket.close();
} catch ( IOException ioe2 )
{
}
}
}
@Override
public String getName()
{
return ( handshake == null ) ? null : handshake.username;
}
@Override
public byte getVersion()
{
return ( handshake == null ) ? -1 : handshake.procolVersion;
}
@Override
public InetSocketAddress getVirtualHost()
{
return ( handshake == null ) ? null : new InetSocketAddress( handshake.host, handshake.port );
}
@Override
public InetSocketAddress getAddress()
{
return (InetSocketAddress) socket.getRemoteSocketAddress();
}
}

View File

@@ -1,14 +0,0 @@
package net.md_5.bungee;
/**
* Exception, which when thrown will disconnect the player from the proxy with
* the specified message.
*/
public class KickException extends RuntimeException
{
public KickException(String message)
{
super( message );
}
}

View File

@@ -1,48 +0,0 @@
package net.md_5.bungee;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import static net.md_5.bungee.Logger.$;
import net.md_5.bungee.api.config.ListenerInfo;
/**
* Thread to listen and dispatch incoming connections to the proxy.
*/
public class ListenThread extends Thread
{
public final ServerSocket socket;
private final ListenerInfo info;
public ListenThread(ListenerInfo info) throws IOException
{
super( "Listen Thread - " + info );
this.info = info;
socket = new ServerSocket();
socket.bind( info.getHost() );
}
@Override
public void run()
{
while ( !isInterrupted() )
{
try
{
Socket client = socket.accept();
BungeeCord.getInstance().setSocketOptions( client );
$().info( client.getInetAddress() + " has connected" );
InitialHandler handler = new InitialHandler( client, info );
BungeeCord.getInstance().threadPool.submit( handler );
} catch ( SocketException ex )
{
ex.printStackTrace(); // Now people can see why their operating system is failing them and stop bitching at me!
} catch ( IOException ex )
{
ex.printStackTrace(); // TODO
}
}
}
}

View File

@@ -8,7 +8,6 @@ import java.io.UnsupportedEncodingException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLEncoder; import java.net.URLEncoder;
import static net.md_5.bungee.Logger.$;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
public class Metrics extends Thread public class Metrics extends Thread
@@ -55,7 +54,7 @@ public class Metrics extends Thread
firstPost = false; firstPost = false;
} catch ( IOException ex ) } catch ( IOException ex )
{ {
$().info( "[Metrics] " + ex.getMessage() ); ProxyServer.getInstance().getLogger().info( "[Metrics] " + ex.getMessage() );
} }
try try
{ {

View File

@@ -1,47 +1,52 @@
package net.md_5.bungee; package net.md_5.bungee;
import io.netty.channel.Channel;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.util.concurrent.TimeUnit;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import lombok.Getter; import lombok.Getter;
import net.md_5.bungee.api.Callback; import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.ServerPing; import lombok.Setter;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.packet.DefinedPacket; import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.packet.Packet1Login; import net.md_5.bungee.packet.Packet1Login;
import net.md_5.bungee.packet.PacketFAPluginMessage; import net.md_5.bungee.packet.PacketFAPluginMessage;
import net.md_5.bungee.packet.PacketStream; import net.md_5.bungee.packet.PacketFFKick;
/** @RequiredArgsConstructor
* Class representing a connection from the proxy to the server; ie upstream. public class ServerConnection implements Server
*/
public class ServerConnection extends GenericConnection implements Server
{ {
@Getter @Getter
private final ServerInfo info; private final ChannelWrapper ch;
public final Packet1Login loginPacket; @Getter
public Queue<DefinedPacket> packetQueue = new ConcurrentLinkedQueue<>(); private final BungeeServerInfo info;
@Getter
public ServerConnection(Socket socket, ServerInfo info, PacketStream stream, Packet1Login loginPacket) private final Packet1Login loginPacket;
{ @Getter
super( socket, stream ); @Setter
this.info = info; private boolean isObsolete;
this.loginPacket = loginPacket;
}
@Override @Override
public void sendData(String channel, byte[] data) public void sendData(String channel, byte[] data)
{ {
packetQueue.add( new PacketFAPluginMessage( channel, data ) ); ch.write( new PacketFAPluginMessage( channel, data ) );
} }
@Override @Override
public void ping(final Callback<ServerPing> callback) public synchronized void disconnect(String reason)
{ {
getInfo().ping( callback ); if ( ch.getHandle().isActive() )
{
ch.write( new PacketFFKick( reason ) );
ch.getHandle().eventLoop().schedule( new Runnable()
{
@Override
public void run()
{
ch.getHandle().close();
}
}, 100, TimeUnit.MILLISECONDS );
}
} }
@Override @Override

View File

@@ -1,45 +1,157 @@
package net.md_5.bungee; package net.md_5.bungee;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import java.io.IOException; import com.google.common.io.ByteArrayDataOutput;
import java.net.Socket; import com.google.common.io.ByteStreams;
import java.util.Objects;
import java.util.Queue; import java.util.Queue;
import lombok.RequiredArgsConstructor;
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.event.ServerConnectedEvent; import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.event.ServerKickEvent;
import net.md_5.bungee.api.scoreboard.Objective;
import net.md_5.bungee.api.scoreboard.Scoreboard;
import net.md_5.bungee.api.scoreboard.Team;
import net.md_5.bungee.connection.CancelSendSignal;
import net.md_5.bungee.connection.DownstreamBridge;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.packet.DefinedPacket; import net.md_5.bungee.packet.DefinedPacket;
import net.md_5.bungee.packet.Packet1Login; import net.md_5.bungee.packet.Packet1Login;
import net.md_5.bungee.packet.Packet9Respawn;
import net.md_5.bungee.packet.PacketCDClientStatus; import net.md_5.bungee.packet.PacketCDClientStatus;
import net.md_5.bungee.packet.PacketCEScoreboardObjective;
import net.md_5.bungee.packet.PacketD1Team;
import net.md_5.bungee.packet.PacketFAPluginMessage;
import net.md_5.bungee.packet.PacketFDEncryptionRequest; import net.md_5.bungee.packet.PacketFDEncryptionRequest;
import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketFFKick;
import net.md_5.bungee.packet.PacketHandler; import net.md_5.bungee.packet.PacketHandler;
import net.md_5.bungee.packet.PacketStream;
@RequiredArgsConstructor
public class ServerConnector extends PacketHandler public class ServerConnector extends PacketHandler
{ {
private final PacketStream stream; private final ProxyServer bungee;
private Packet1Login loginPacket; private ChannelWrapper ch;
private final UserConnection user;
private final BungeeServerInfo target;
private State thisState = State.ENCRYPT_REQUEST; private State thisState = State.ENCRYPT_REQUEST;
public ServerConnector(PacketStream stream)
{
this.stream = stream;
}
private enum State private enum State
{ {
ENCRYPT_REQUEST, LOGIN, FINISHED; ENCRYPT_REQUEST, LOGIN, FINISHED;
} }
@Override
public void connected(ChannelWrapper channel) throws Exception
{
this.ch = channel;
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF( "Login" );
out.writeUTF( user.getAddress().getAddress().getHostAddress() );
out.writeInt( user.getAddress().getPort() );
channel.write( new PacketFAPluginMessage( "BungeeCord", out.toByteArray() ) );
channel.write( user.getPendingConnection().getHandshake() );
channel.write( PacketCDClientStatus.CLIENT_LOGIN );
}
@Override
public void disconnected(ChannelWrapper channel) throws Exception
{
user.getPendingConnects().remove( target );
}
@Override @Override
public void handle(Packet1Login login) throws Exception public void handle(Packet1Login login) throws Exception
{ {
Preconditions.checkState( thisState == State.LOGIN, "Not exepcting LOGIN" ); Preconditions.checkState( thisState == State.LOGIN, "Not exepcting LOGIN" );
loginPacket = login;
ServerConnection server = new ServerConnection( ch, target, login );
ServerConnectedEvent event = new ServerConnectedEvent( user, server );
bungee.getPluginManager().callEvent( event );
ch.write( BungeeCord.getInstance().registerChannels() );
// TODO: Race conditions with many connects
Queue<DefinedPacket> packetQueue = ( (BungeeServerInfo) target ).getPacketQueue();
while ( !packetQueue.isEmpty() )
{
ch.write( packetQueue.poll() );
}
if ( user.getSettings() != null )
{
ch.write( user.getSettings() );
}
synchronized ( user.getSwitchMutex() )
{
if ( user.getServer() == null )
{
// Once again, first connection
user.setClientEntityId( login.entityId );
user.setServerEntityId( login.entityId );
// Set tab list size
Packet1Login modLogin = new Packet1Login(
login.entityId,
login.levelType,
login.gameMode,
(byte) login.dimension,
login.difficulty,
login.unused,
(byte) user.getPendingConnection().getListener().getTabListSize() );
user.sendPacket( modLogin );
} else
{
bungee.getTabListHandler().onServerChange( user );
Scoreboard serverScoreboard = user.getServerSentScoreboard();
for ( Objective objective : serverScoreboard.getObjectives() )
{
user.sendPacket( new PacketCEScoreboardObjective( objective.getName(), objective.getValue(), (byte) 1 ) );
}
for ( Team team : serverScoreboard.getTeams() )
{
user.sendPacket( PacketD1Team.destroy( team.getName() ) );
}
serverScoreboard.clear();
user.sendPacket( Packet9Respawn.DIM1_SWITCH );
user.sendPacket( Packet9Respawn.DIM2_SWITCH );
user.setServerEntityId( login.entityId );
user.sendPacket( new Packet9Respawn( login.dimension, login.difficulty, login.gameMode, (short) 256, login.levelType ) );
// Remove from old servers
user.getServer().setObsolete( true );
user.getServer().disconnect( "Quitting" );
}
// TODO: Fix this?
if ( !user.isActive() )
{
server.disconnect( "Quitting" );
// Silly server admins see stack trace and die
bungee.getLogger().warning( "No client connected for pending server!" );
return;
}
// Add to new server
// TODO: Move this to the connected() method of DownstreamBridge
target.addPlayer( user );
user.getPendingConnects().remove( target );
user.setServer( server );
ch.getHandle().pipeline().get( HandlerBoss.class ).setHandler( new DownstreamBridge( bungee, user, server ) );
}
thisState = State.FINISHED; thisState = State.FINISHED;
throw new CancelSendSignal();
} }
@Override @Override
@@ -52,66 +164,31 @@ public class ServerConnector extends PacketHandler
@Override @Override
public void handle(PacketFFKick kick) throws Exception public void handle(PacketFFKick kick) throws Exception
{ {
throw new KickException( kick.message ); ServerInfo def = bungee.getServerInfo( user.getPendingConnection().getListener().getFallbackServer() );
} if ( Objects.equals( target, def ) )
public static ServerConnection connect(UserConnection user, ServerInfo info, boolean retry)
{
Socket socket = null;
try
{ {
socket = new Socket(); def = null;
socket.connect( info.getAddress(), BungeeCord.getInstance().config.getTimeout() ); }
BungeeCord.getInstance().setSocketOptions( socket ); ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( user, kick.message, def ) );
PacketStream stream = new PacketStream( socket.getInputStream(), socket.getOutputStream(), user.stream.getProtocol() ); if ( event.isCancelled() && event.getCancelServer() != null )
ServerConnector connector = new ServerConnector( stream );
stream.write( user.handshake );
stream.write( PacketCDClientStatus.CLIENT_LOGIN );
while ( connector.thisState != State.FINISHED )
{
byte[] buf = stream.readPacket();
DefinedPacket packet = DefinedPacket.packet( buf );
packet.handle( connector );
}
ServerConnection server = new ServerConnection( socket, info, stream, connector.loginPacket );
ServerConnectedEvent event = new ServerConnectedEvent( user, server );
ProxyServer.getInstance().getPluginManager().callEvent( event );
stream.write( BungeeCord.getInstance().registerChannels() );
Queue<DefinedPacket> packetQueue = ( (BungeeServerInfo) info ).getPacketQueue();
while ( !packetQueue.isEmpty() )
{
stream.write( packetQueue.poll() );
}
return server;
} catch ( Exception ex )
{ {
if ( socket != null ) user.connect( event.getCancelServer() );
{ return;
try }
{
socket.close(); String message = ChatColor.RED + "Kicked whilst connecting to " + target.getName() + ": " + kick.message;
} catch ( IOException ioe ) if ( user.getServer() == null )
{ {
} user.disconnect( message );
} } else
ServerInfo def = ProxyServer.getInstance().getServers().get( user.getPendingConnection().getListener().getDefaultServer() ); {
if ( retry && !info.equals( def ) ) user.sendMessage( message );
{
user.sendMessage( ChatColor.RED + "Could not connect to target server, you have been moved to the default server" );
return connect( user, def, false );
} else
{
if ( ex instanceof RuntimeException )
{
throw (RuntimeException) ex;
}
throw new RuntimeException( "Could not connect to target server " + Util.exception( ex ) );
}
} }
} }
@Override
public String toString()
{
return "[" + user.getName() + "] <-> ServerConnector [" + target.getName() + "]";
}
} }

View File

@@ -1,224 +1,270 @@
package net.md_5.bungee; package net.md_5.bungee;
import java.io.ByteArrayInputStream; import com.google.common.base.Preconditions;
import java.io.ByteArrayOutputStream; import io.netty.bootstrap.Bootstrap;
import java.io.DataInputStream; import io.netty.channel.Channel;
import java.io.DataOutputStream; import io.netty.channel.ChannelFuture;
import java.io.IOException; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.Objects;
import java.util.Map; import java.util.logging.Level;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import lombok.Getter; import lombok.Getter;
import lombok.Synchronized; import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
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.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ChatEvent; import net.md_5.bungee.api.event.PermissionCheckEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.packet.*; import net.md_5.bungee.api.scoreboard.Scoreboard;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.packet.DefinedPacket;
import net.md_5.bungee.packet.Packet3Chat;
import net.md_5.bungee.packet.Packet9Respawn;
import net.md_5.bungee.packet.PacketCCSettings;
import net.md_5.bungee.packet.PacketFAPluginMessage;
import net.md_5.bungee.packet.PacketFFKick;
public final class UserConnection extends GenericConnection implements ProxiedPlayer @RequiredArgsConstructor
public final class UserConnection implements ProxiedPlayer
{ {
public final Packet2Handshake handshake; /*========================================================================*/
final Packet1Login forgeLogin; @NonNull
final List<PacketFAPluginMessage> loginMessages; private final ProxyServer bungee;
public Queue<DefinedPacket> packetQueue = new ConcurrentLinkedQueue<>(); @NonNull
private final ChannelWrapper ch;
@Getter @Getter
private final PendingConnection pendingConnection; @NonNull
private final String name;
@Getter @Getter
@NonNull
private final InitialHandler pendingConnection;
/*========================================================================*/
@Getter
@Setter
private ServerConnection server; private ServerConnection server;
private UpstreamBridge upBridge;
private DownstreamBridge downBridge;
// reconnect stuff
private int clientEntityId;
private int serverEntityId;
private volatile boolean reconnecting;
// ping stuff
private int trackingPingId;
private long pingTime;
@Getter @Getter
private int ping = 1000; private final Object switchMutex = new Object();
// Permissions @Getter
private final Collection<ServerInfo> pendingConnects = new HashSet<>();
/*========================================================================*/
@Getter
@Setter
private int sentPingId;
@Getter
@Setter
private long sentPingTime;
@Getter
@Setter
private int ping = 100;
/*========================================================================*/
private final Collection<String> groups = new HashSet<>(); private final Collection<String> groups = new HashSet<>();
private final Map<String, Boolean> permissions = new HashMap<>(); private final Collection<String> permissions = new HashSet<>();
private final Object permMutex = new Object(); /*========================================================================*/
// Hack for connect timings @Getter
private ServerInfo nextServer; private int clientEntityId;
private volatile boolean clientConnected = true; @Getter
@Setter
private int serverEntityId;
@Getter
@Setter
private PacketCCSettings settings;
@Getter
private final Scoreboard serverSentScoreboard = new Scoreboard();
/*========================================================================*/
@Getter
private String displayName;
/*========================================================================*/
public UserConnection(Socket socket, PendingConnection pendingConnection, PacketStream stream, Packet2Handshake handshake, Packet1Login forgeLogin, List<PacketFAPluginMessage> loginMessages) public void init()
{ {
super( socket, stream ); this.displayName = name;
this.handshake = handshake;
this.pendingConnection = pendingConnection;
this.forgeLogin = forgeLogin;
this.loginMessages = loginMessages;
name = handshake.username.substring( 0, Math.min( handshake.username.length(), 16 ) );
displayName = name;
Collection<String> g = ProxyServer.getInstance().getConfigurationAdapter().getGroups( name ); Collection<String> g = bungee.getConfigurationAdapter().getGroups( name );
for ( String s : g ) for ( String s : g )
{ {
addGroups( s ); addGroups( s );
} }
} }
public void sendPacket(DefinedPacket p)
{
ch.write( p );
}
public void sendPacket(byte[] b)
{
ch.write( b );
}
@Deprecated
public boolean isActive()
{
return ch.getHandle().isActive();
}
@Override @Override
public void setDisplayName(String name) public void setDisplayName(String name)
{ {
ProxyServer.getInstance().getTabListHandler().onDisconnect( this ); Preconditions.checkNotNull( name, "displayName" );
Preconditions.checkArgument( name.length() <= 16, "Display name cannot be longer than 16 characters" );
bungee.getTabListHandler().onDisconnect( this );
displayName = name; displayName = name;
ProxyServer.getInstance().getTabListHandler().onConnect( this ); bungee.getTabListHandler().onConnect( this );
} }
@Override @Override
public void connect(ServerInfo target) public void connect(ServerInfo target)
{ {
nextServer = target; connect( target, false );
} }
public void connect(ServerInfo target, boolean force) public void connectNow(ServerInfo target)
{ {
nextServer = null; sendPacket( Packet9Respawn.DIM1_SWITCH );
if ( server == null ) sendPacket( Packet9Respawn.DIM2_SWITCH );
connect( target );
}
public void connect(ServerInfo info, final boolean retry)
{
ServerConnectEvent event = new ServerConnectEvent( this, info );
ProxyServer.getInstance().getPluginManager().callEvent( event );
Preconditions.checkArgument( event.getTarget() instanceof BungeeServerInfo, "BungeeCord can only connect to BungeeServerInfo instances" );
final BungeeServerInfo target = (BungeeServerInfo) event.getTarget(); // Update in case the event changed target
if ( getServer() != null && Objects.equals( getServer().getInfo(), target ) )
{ {
// First join sendMessage( ChatColor.RED + "Cannot connect to server you are already on!" );
BungeeCord.getInstance().connections.put( name, this ); return;
ProxyServer.getInstance().getTabListHandler().onConnect( this ); }
if ( pendingConnects.contains( target ) )
{
sendMessage( ChatColor.RED + "Already connecting to this server!" );
return;
} }
ServerConnectEvent event = new ServerConnectEvent( this, target ); pendingConnects.add( target );
BungeeCord.getInstance().getPluginManager().callEvent( event );
target = event.getTarget(); // Update in case the event changed target
ProxyServer.getInstance().getTabListHandler().onServerChange( this ); new Bootstrap()
try .channel( NioSocketChannel.class )
.group( BungeeCord.getInstance().eventLoops )
.handler( new ChannelInitializer()
{ {
reconnecting = true; @Override
protected void initChannel(Channel ch) throws Exception
if ( server != null )
{ {
stream.write( new Packet9Respawn( (byte) 1, (byte) 0, (byte) 0, (short) 256, "DEFAULT" ) ); PipelineUtils.BASE.initChannel( ch );
stream.write( new Packet9Respawn( (byte) -1, (byte) 0, (byte) 0, (short) 256, "DEFAULT" ) ); ch.pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( bungee, UserConnection.this, target ) );
} }
} )
ServerConnection newServer = ServerConnector.connect( this, target, true ); .option( ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000 ) // TODO: Configurable
if ( server == null ) .remoteAddress( target.getAddress() )
.connect().addListener( new ChannelFutureListener()
{
@Override
public void operationComplete(ChannelFuture future) throws Exception
{ {
// Once again, first connection if ( !future.isSuccess() )
clientEntityId = newServer.loginPacket.entityId; {
serverEntityId = newServer.loginPacket.entityId; future.channel().close();
// Set tab list size pendingConnects.remove( target );
Packet1Login s = newServer.loginPacket;
Packet1Login login = new Packet1Login( s.entityId, s.levelType, s.gameMode, (byte) s.dimension, s.difficulty, s.unused, (byte) pendingConnection.getListener().getTabListSize() );
stream.write( login );
stream.write( BungeeCord.getInstance().registerChannels() );
upBridge = new UpstreamBridge(); ServerInfo def = ProxyServer.getInstance().getServers().get( getPendingConnection().getListener().getFallbackServer() );
upBridge.start(); if ( retry & target != def && ( getServer() == null || def != getServer().getInfo() ) )
} else {
{ sendMessage( ChatColor.RED + "Could not connect to target server, you have been moved to the lobby server" );
try connect( def, false );
{ } else
downBridge.interrupt(); {
downBridge.join(); if ( server == null )
} catch ( InterruptedException ie ) {
{ disconnect( "Could not connect to default server, please try again later: " + future.cause().getClass().getName() );
} else
{
sendMessage( ChatColor.RED + "Could not connect to selected server, please try again later: " + future.cause().getClass().getName() );
}
}
} }
server.disconnect( "Quitting" );
server.getInfo().removePlayer( this );
Packet1Login login = newServer.loginPacket;
serverEntityId = login.entityId;
stream.write( new Packet9Respawn( login.dimension, login.difficulty, login.gameMode, (short) 256, login.levelType ) );
} }
} );
// Reconnect process has finished, lets get the player moving again
reconnecting = false;
// Add to new
target.addPlayer( this );
// Start the bridges and move on
server = newServer;
downBridge = new DownstreamBridge();
downBridge.start();
} catch ( KickException ex )
{
disconnect( ex.getMessage() );
} catch ( Exception ex )
{
disconnect( "Could not connect to server - " + Util.exception( ex ) );
}
} }
@Override @Override
public synchronized void disconnect(String reason) public synchronized void disconnect(String reason)
{ {
if ( clientConnected ) if ( ch.getHandle().isActive() )
{ {
PlayerDisconnectEvent event = new PlayerDisconnectEvent( this ); bungee.getLogger().log( Level.INFO, "[" + getName() + "] disconnected with: " + reason );
ProxyServer.getInstance().getPluginManager().callEvent( event ); sendPacket( new PacketFFKick( reason ) );
ProxyServer.getInstance().getTabListHandler().onDisconnect( this ); ch.getHandle().close();
ProxyServer.getInstance().getPlayers().remove( this );
super.disconnect( reason );
if ( server != null ) if ( server != null )
{ {
server.getInfo().removePlayer( this );
server.disconnect( "Quitting" ); server.disconnect( "Quitting" );
ProxyServer.getInstance().getReconnectHandler().setServer( this );
} }
clientConnected = false;
} }
} }
@Override
public void chat(String message)
{
Preconditions.checkState( server != null, "Not connected to server" );
server.getCh().write( new Packet3Chat( message ) );
}
@Override @Override
public void sendMessage(String message) public void sendMessage(String message)
{ {
packetQueue.add( new Packet3Chat( message ) ); sendPacket( new Packet3Chat( message ) );
}
@Override
public void sendMessages(String... messages)
{
for ( String message : messages )
{
sendMessage( message );
}
} }
@Override @Override
public void sendData(String channel, byte[] data) public void sendData(String channel, byte[] data)
{ {
server.packetQueue.add( new PacketFAPluginMessage( channel, data ) ); sendPacket( new PacketFAPluginMessage( channel, data ) );
} }
@Override @Override
public InetSocketAddress getAddress() public InetSocketAddress getAddress()
{ {
return (InetSocketAddress) socket.getRemoteSocketAddress(); return (InetSocketAddress) ch.getHandle().remoteAddress();
} }
@Override @Override
@Synchronized("permMutex")
public Collection<String> getGroups() public Collection<String> getGroups()
{ {
return Collections.unmodifiableCollection( groups ); return Collections.unmodifiableCollection( groups );
} }
@Override @Override
@Synchronized("permMutex")
public void addGroups(String... groups) public void addGroups(String... groups)
{ {
for ( String group : groups ) for ( String group : groups )
{ {
this.groups.add( group ); this.groups.add( group );
for ( String permission : ProxyServer.getInstance().getConfigurationAdapter().getPermissions( group ) ) for ( String permission : bungee.getConfigurationAdapter().getPermissions( group ) )
{ {
setPermission( permission, true ); setPermission( permission, true );
} }
@@ -226,13 +272,12 @@ public final class UserConnection extends GenericConnection implements ProxiedPl
} }
@Override @Override
@Synchronized("permMutex")
public void removeGroups(String... groups) public void removeGroups(String... groups)
{ {
for ( String group : groups ) for ( String group : groups )
{ {
this.groups.remove( group ); this.groups.remove( group );
for ( String permission : ProxyServer.getInstance().getConfigurationAdapter().getPermissions( group ) ) for ( String permission : bungee.getConfigurationAdapter().getPermissions( group ) )
{ {
setPermission( permission, false ); setPermission( permission, false );
} }
@@ -240,306 +285,32 @@ public final class UserConnection extends GenericConnection implements ProxiedPl
} }
@Override @Override
@Synchronized("permMutex")
public boolean hasPermission(String permission) public boolean hasPermission(String permission)
{ {
Boolean val = permissions.get( permission ); return bungee.getPluginManager().callEvent( new PermissionCheckEvent( this, permission, permissions.contains( permission ) ) ).hasPermission();
return ( val == null ) ? false : val;
} }
@Override @Override
@Synchronized("permMutex")
public void setPermission(String permission, boolean value) public void setPermission(String permission, boolean value)
{ {
permissions.put( permission, value ); if ( value )
}
private class UpstreamBridge extends Thread
{
public UpstreamBridge()
{ {
super( "Upstream Bridge - " + name ); permissions.add( permission );
} } else
@Override
public void run()
{ {
while ( !socket.isClosed() ) permissions.remove( permission );
{
try
{
byte[] packet = stream.readPacket();
boolean sendPacket = true;
int id = Util.getId( packet );
switch ( id )
{
case 0x00:
if ( trackingPingId == new Packet0KeepAlive( packet ).id )
{
int newPing = (int) ( System.currentTimeMillis() - pingTime );
ProxyServer.getInstance().getTabListHandler().onPingChange( UserConnection.this, newPing );
ping = newPing;
}
break;
case 0x03:
Packet3Chat chat = new Packet3Chat( packet );
if ( chat.message.startsWith( "/" ) )
{
sendPacket = !ProxyServer.getInstance().getPluginManager().dispatchCommand( UserConnection.this, chat.message.substring( 1 ) );
} else
{
ChatEvent chatEvent = new ChatEvent( UserConnection.this, server, chat.message );
ProxyServer.getInstance().getPluginManager().callEvent( chatEvent );
sendPacket = !chatEvent.isCancelled();
}
break;
case 0xFA:
// Call the onPluginMessage event
PacketFAPluginMessage message = new PacketFAPluginMessage( packet );
// Might matter in the future
if ( message.tag.equals( "BungeeCord" ) )
{
continue;
}
PluginMessageEvent event = new PluginMessageEvent( UserConnection.this, server, message.tag, message.data );
ProxyServer.getInstance().getPluginManager().callEvent( event );
if ( event.isCancelled() )
{
continue;
}
break;
}
while ( !server.packetQueue.isEmpty() )
{
DefinedPacket p = server.packetQueue.poll();
if ( p != null )
{
server.stream.write( p );
}
}
EntityMap.rewrite( packet, clientEntityId, serverEntityId );
if ( sendPacket && !server.socket.isClosed() )
{
server.stream.write( packet );
}
try
{
Thread.sleep( BungeeCord.getInstance().config.getSleepTime() );
} catch ( InterruptedException ex )
{
}
} catch ( IOException ex )
{
disconnect( "Reached end of stream" );
} catch ( Exception ex )
{
disconnect( Util.exception( ex ) );
}
}
} }
} }
private class DownstreamBridge extends Thread @Override
public String toString()
{ {
return name;
}
public DownstreamBridge() public void setClientEntityId(int clientEntityId)
{ {
super( "Downstream Bridge - " + name ); Preconditions.checkState( this.clientEntityId == 0, "Client entityId already set!" );
} this.clientEntityId = clientEntityId;
@Override
public void run()
{
try
{
outer:
while ( !reconnecting )
{
byte[] packet = server.stream.readPacket();
int id = Util.getId( packet );
switch ( id )
{
case 0x00:
trackingPingId = new Packet0KeepAlive( packet ).id;
pingTime = System.currentTimeMillis();
break;
case 0x03:
Packet3Chat chat = new Packet3Chat( packet );
ChatEvent chatEvent = new ChatEvent( server, UserConnection.this, chat.message );
ProxyServer.getInstance().getPluginManager().callEvent( chatEvent );
if ( chatEvent.isCancelled() )
{
continue;
}
break;
case 0xC9:
PacketC9PlayerListItem playerList = new PacketC9PlayerListItem( packet );
if ( !ProxyServer.getInstance().getTabListHandler().onListUpdate( UserConnection.this, playerList.username, playerList.online, playerList.ping ) )
{
continue;
}
break;
case 0xFA:
// Call the onPluginMessage event
PacketFAPluginMessage message = new PacketFAPluginMessage( packet );
DataInputStream in = new DataInputStream( new ByteArrayInputStream( message.data ) );
PluginMessageEvent event = new PluginMessageEvent( server, UserConnection.this, message.tag, message.data );
ProxyServer.getInstance().getPluginManager().callEvent( event );
if ( event.isCancelled() )
{
continue;
}
if ( message.tag.equals( "BungeeCord" ) )
{
String subChannel = in.readUTF();
if ( subChannel.equals( "Forward" ) )
{
String target = in.readUTF();
String channel = in.readUTF();
short len = in.readShort();
byte[] data = new byte[ len ];
in.readFully( data );
ByteArrayOutputStream b = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream( b );
out.writeUTF( channel );
out.writeShort( data.length );
out.write( data );
if ( target.equals( "ALL" ) )
{
for ( ServerInfo server : BungeeCord.getInstance().getServers().values() )
{
server.sendData( "BungeeCord", b.toByteArray() );
}
} else
{
ServerInfo server = BungeeCord.getInstance().getServerInfo( target );
if ( server != null )
{
server.sendData( "BungeeCord", b.toByteArray() );
}
}
}
if ( subChannel.equals( "Connect" ) )
{
ServerInfo server = ProxyServer.getInstance().getServerInfo( in.readUTF() );
if ( server != null )
{
connect( server, true );
break outer;
}
}
if ( subChannel.equals( "IP" ) )
{
ByteArrayOutputStream b = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream( b );
out.writeUTF( "IP" );
out.writeUTF( getAddress().getHostString() );
out.writeInt( getAddress().getPort() );
getServer().sendData( "BungeeCord", b.toByteArray() );
}
if ( subChannel.equals( "PlayerCount" ) )
{
ServerInfo server = ProxyServer.getInstance().getServerInfo( in.readUTF() );
if ( server != null )
{
ByteArrayOutputStream b = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream( b );
out.writeUTF( "PlayerCount" );
out.writeUTF( server.getName() );
out.writeInt( server.getPlayers().size() );
getServer().sendData( "BungeeCord", b.toByteArray() );
}
}
if ( subChannel.equals( "PlayerList" ) )
{
ServerInfo server = ProxyServer.getInstance().getServerInfo( in.readUTF() );
if ( server != null )
{
ByteArrayOutputStream b = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream( b );
out.writeUTF( "PlayerList" );
out.writeUTF( server.getName() );
StringBuilder sb = new StringBuilder();
for ( ProxiedPlayer p : server.getPlayers() )
{
sb.append( p.getName() );
sb.append( "," );
}
out.writeUTF( sb.substring( 0, sb.length() - 1 ) );
getServer().sendData( "BungeeCord", b.toByteArray() );
}
}
if ( subChannel.equals( "GetServers" ) )
{
ByteArrayOutputStream b = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream( b );
out.writeUTF( "GetServers" );
StringBuilder sb = new StringBuilder();
for ( String server : ProxyServer.getInstance().getServers().keySet() )
{
sb.append( server );
sb.append( "," );
}
out.writeUTF( sb.substring( 0, sb.length() - 1 ) );
getServer().sendData( "BungeeCord", b.toByteArray() );
}
if ( subChannel.equals( "Message" ) )
{
ProxiedPlayer target = ProxyServer.getInstance().getPlayer( in.readUTF() );
if ( target != null )
{
target.sendMessage( in.readUTF() );
}
}
continue;
}
break;
case 0xFF:
disconnect( new PacketFFKick( packet ).message );
break outer;
}
while ( !packetQueue.isEmpty() )
{
DefinedPacket p = packetQueue.poll();
if ( p != null )
{
stream.write( p );
}
}
EntityMap.rewrite( packet, serverEntityId, clientEntityId );
stream.write( packet );
if ( nextServer != null )
{
connect( nextServer, true );
break outer;
}
}
} catch ( Exception ex )
{
disconnect( Util.exception( ex ) );
}
}
} }
} }

View File

@@ -1,6 +1,8 @@
package net.md_5.bungee; package net.md_5.bungee;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.List;
/** /**
* Series of utility classes to perform various operations. * Series of utility classes to perform various operations.
@@ -27,39 +29,6 @@ public class Util
return new InetSocketAddress( split[0], port ); return new InetSocketAddress( split[0], port );
} }
/**
* Gets the value of the first unsigned byte of the specified array. Useful
* for getting the id of a packet array .
*
* @param b the array to read from
* @return the unsigned value of the first byte
*/
public static int getId(byte[] b)
{
return b[0] & 0xFF;
}
/**
* Normalizes a config path by prefix upper case letters with '_' and
* turning them to lowercase.
*
* @param s the string to normalize
* @return the normalized path
*/
public static String normalize(String s)
{
StringBuilder result = new StringBuilder();
for ( char c : s.toCharArray() )
{
if ( Character.isUpperCase( c ) )
{
result.append( "_" );
}
result.append( Character.toLowerCase( c ) );
}
return result.toString();
}
/** /**
* Formats an integer as a hex value. * Formats an integer as a hex value.
* *
@@ -80,6 +49,26 @@ public class Util
*/ */
public static String exception(Throwable t) public static String exception(Throwable t)
{ {
return t.getClass().getSimpleName() + " : " + t.getMessage() + " @ " + t.getStackTrace()[0].getClassName() + ":" + t.getStackTrace()[0].getLineNumber(); // TODO: We should use clear manually written exceptions
StackTraceElement[] trace = t.getStackTrace();
return t.getClass().getSimpleName() + " : " + t.getMessage()
+ ( ( trace.length > 0 ) ? " @ " + t.getStackTrace()[0].getClassName() + ":" + t.getStackTrace()[0].getLineNumber() : "" );
}
public static String csv(Collection<?> objects)
{
return format( objects, ", " );
}
public static String format(Collection<?> objects, String separators)
{
StringBuilder ret = new StringBuilder();
for ( Object o : objects )
{
ret.append( o );
ret.append( separators );
}
return ( ret.length() == 0 ) ? "" : ret.substring( 0, ret.length() - separators.length() );
} }
} }

View File

@@ -6,8 +6,8 @@ import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level; import java.util.logging.Level;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ReconnectHandler; import net.md_5.bungee.api.ReconnectHandler;
@@ -41,10 +41,7 @@ public class YamlReconnectHandler implements ReconnectHandler
if ( data == null ) if ( data == null )
{ {
data = new ConcurrentHashMap<>(); data = new HashMap<>();
} else
{
data = new ConcurrentHashMap<>( data );
} }
} }
@@ -53,15 +50,14 @@ public class YamlReconnectHandler implements ReconnectHandler
{ {
ListenerInfo listener = player.getPendingConnection().getListener(); ListenerInfo listener = player.getPendingConnection().getListener();
String name; String name;
if ( listener.isForceDefault() ) String forced = listener.getForcedHosts().get( player.getPendingConnection().getVirtualHost().getHostName().toLowerCase() );
if ( forced == null && listener.isForceDefault() )
{ {
name = listener.getDefaultServer(); forced = listener.getDefaultServer();
} else
{
String forced = listener.getForcedHosts().get( player.getPendingConnection().getVirtualHost().getHostName() );
String server = ( forced == null ) ? data.get( key( player ) ) : forced;
name = ( server != null ) ? server : listener.getDefaultServer();
} }
String server = ( forced == null ) ? data.get( key( player ) ) : forced;
name = ( server != null ) ? server : listener.getDefaultServer();
ServerInfo info = ProxyServer.getInstance().getServerInfo( name ); ServerInfo info = ProxyServer.getInstance().getServerInfo( name );
if ( info == null ) if ( info == null )
{ {
@@ -84,7 +80,7 @@ public class YamlReconnectHandler implements ReconnectHandler
} }
@Override @Override
public void save() public synchronized void save()
{ {
try ( FileWriter wr = new FileWriter( file ) ) try ( FileWriter wr = new FileWriter( file ) )
{ {

View File

@@ -44,6 +44,7 @@ public class CommandAlert extends Command
{ {
player.sendMessage( message ); player.sendMessage( message );
} }
ProxyServer.getInstance().getConsole().sendMessage( message );
} }
} }
} }

View File

@@ -1,9 +1,14 @@
package net.md_5.bungee.command; package net.md_5.bungee.command;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.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.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;
@@ -21,23 +26,38 @@ public class CommandList extends Command
@Override @Override
public void execute(CommandSender sender, String[] args) public void execute(CommandSender sender, String[] args)
{ {
StringBuilder users = new StringBuilder(); for ( ServerInfo server : ProxyServer.getInstance().getServers().values() )
Collection<ProxiedPlayer> connections = ProxyServer.getInstance().getPlayers();
if ( connections.isEmpty() )
{ {
sender.sendMessage( ChatColor.BLUE + "Currently no players online." ); if ( !server.canAccess( sender ) )
return; {
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<>();
for ( ProxiedPlayer player : serverPlayers )
{
players.add( player.getDisplayName() );
}
Collections.sort( players, String.CASE_INSENSITIVE_ORDER );
message.append( Util.format( players, ChatColor.RESET + ", " ) );
sender.sendMessage( message.toString() );
} }
for ( ProxiedPlayer player : connections ) sender.sendMessage( "Total players online: " + ProxyServer.getInstance().getPlayers().size() );
{
users.append( player.getDisplayName() );
users.append( ", " );
users.append( ChatColor.RESET );
}
users.setLength( users.length() - 2 );
sender.sendMessage( ChatColor.BLUE + "Currently online across all servers (" + connections.size() + "): " + ChatColor.RESET + users );
} }
} }

View File

@@ -0,0 +1,69 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
public class CommandSend extends Command
{
public CommandSend()
{
super( "send", "bungeecord.command.send" );
}
@Override
public void execute(CommandSender sender, String[] args)
{
if ( args.length != 2 )
{
sender.sendMessage( ChatColor.RED + "Not enough arguments, usage: /send <player|all|current> <target>" );
return;
}
ServerInfo target = ProxyServer.getInstance().getServerInfo( args[1] );
if ( target == null )
{
sender.sendMessage( ChatColor.RED + "Target server does not exist" );
}
if ( args[0].equalsIgnoreCase( "all" ) )
{
for ( ProxiedPlayer p : ProxyServer.getInstance().getPlayers() )
{
summon( p, target, sender );
}
} else if ( args[0].equalsIgnoreCase( "current" ) )
{
if ( !( sender instanceof ProxiedPlayer ) )
{
sender.sendMessage( ChatColor.RED + "Only in game players can use this command" );
}
ProxiedPlayer player = (ProxiedPlayer) sender;
for ( ProxiedPlayer p : player.getServer().getInfo().getPlayers() )
{
summon( p, target, sender );
}
} else
{
ProxiedPlayer player = ProxyServer.getInstance().getPlayer( args[0] );
if ( player == null )
{
sender.sendMessage( ChatColor.RED + "That player is not online" );
}
summon( player, target, sender );
}
sender.sendMessage( ChatColor.GREEN + "Successfully summoned player(s)" );
}
private void summon(ProxiedPlayer player, ServerInfo target, CommandSender sender)
{
if ( player.getServer() != null && !player.getServer().getInfo().equals( target ) )
{
player.connect( target );
player.sendMessage( ChatColor.GOLD + "Summoned to " + target.getName() + " by " + sender.getName() );
}
}
}

View File

@@ -1,9 +1,9 @@
package net.md_5.bungee.command; package net.md_5.bungee.command;
import java.util.Map; import java.util.Map;
import net.md_5.bungee.BungeeCord;
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.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;
@@ -27,16 +27,22 @@ public class CommandServer extends Command
return; return;
} }
ProxiedPlayer player = (ProxiedPlayer) sender; ProxiedPlayer player = (ProxiedPlayer) sender;
Map<String, ServerInfo> servers = BungeeCord.getInstance().config.getServers(); Map<String, ServerInfo> servers = ProxyServer.getInstance().getServers();
if ( args.length == 0 ) if ( args.length == 0 )
{ {
StringBuilder serverList = new StringBuilder(); StringBuilder serverList = new StringBuilder();
for ( String server : servers.keySet() ) for ( ServerInfo server : servers.values() )
{ {
serverList.append( server ); if ( server.canAccess( player ) )
serverList.append( ", " ); {
serverList.append( server.getName() );
serverList.append( ", " );
}
}
if ( serverList.length() != 0 )
{
serverList.setLength( serverList.length() - 2 );
} }
serverList.setLength( serverList.length() - 2 );
player.sendMessage( ChatColor.GOLD + "You may connect to the following servers at this time: " + serverList.toString() ); player.sendMessage( ChatColor.GOLD + "You may connect to the following servers at this time: " + serverList.toString() );
} else } else
{ {
@@ -44,9 +50,9 @@ public class CommandServer extends Command
if ( server == null ) if ( server == null )
{ {
player.sendMessage( ChatColor.RED + "The specified server does not exist" ); player.sendMessage( ChatColor.RED + "The specified server does not exist" );
} else if ( server.equals( player.getServer().getInfo() ) ) } else if ( !server.canAccess( player ) )
{ {
player.sendMessage( ChatColor.RED + "You are already on this server." ); player.sendMessage( ChatColor.RED + "You don't have permission to access this server" );
} else } else
{ {
player.connect( server ); player.connect( server );

View File

@@ -25,6 +25,15 @@ public class ConsoleCommandSender implements CommandSender
System.out.println( ChatColor.stripColor( message ) ); System.out.println( ChatColor.stripColor( message ) );
} }
@Override
public void sendMessages(String... messages)
{
for ( String message : messages )
{
sendMessage( message );
}
}
@Override @Override
public String getName() public String getName()
{ {

View File

@@ -9,9 +9,9 @@ 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.tablist.GlobalPingTabList; import net.md_5.bungee.tablist.GlobalPing;
import net.md_5.bungee.tablist.GlobalTabList; import net.md_5.bungee.tablist.Global;
import net.md_5.bungee.tablist.ServerUniqueTabList; import net.md_5.bungee.tablist.ServerUnique;
/** /**
* Core configuration for the proxy. * Core configuration for the proxy.
@@ -48,7 +48,7 @@ public class Configuration
* Should we check minecraft.net auth. * Should we check minecraft.net auth.
*/ */
private boolean onlineMode = true; private boolean onlineMode = true;
private int sleepTime = 1; private int playerLimit = -1;
public void load() public void load()
{ {
@@ -58,7 +58,7 @@ public class Configuration
timeout = adapter.getInt( "timeout", timeout ); timeout = adapter.getInt( "timeout", timeout );
uuid = adapter.getString( "stats", uuid ); uuid = adapter.getString( "stats", uuid );
onlineMode = adapter.getBoolean( "online_mode", onlineMode ); onlineMode = adapter.getBoolean( "online_mode", onlineMode );
sleepTime = adapter.getInt( "sleep_time", sleepTime ); playerLimit = adapter.getInt( "player_limit", playerLimit );
DefaultTabList tab = DefaultTabList.valueOf( adapter.getString( "tab_list", "GLOBAL_PING" ) ); DefaultTabList tab = DefaultTabList.valueOf( adapter.getString( "tab_list", "GLOBAL_PING" ) );
if ( tab == null ) if ( tab == null )
@@ -68,25 +68,46 @@ public class Configuration
switch ( tab ) switch ( tab )
{ {
case GLOBAL: case GLOBAL:
ProxyServer.getInstance().setTabListHandler( new GlobalTabList() ); ProxyServer.getInstance().setTabListHandler( new Global() );
break; break;
case GLOBAL_PING: case GLOBAL_PING:
ProxyServer.getInstance().setTabListHandler( new GlobalPingTabList() ); ProxyServer.getInstance().setTabListHandler( new GlobalPing() );
break; break;
case SERVER: case SERVER:
ProxyServer.getInstance().setTabListHandler( new ServerUniqueTabList() ); ProxyServer.getInstance().setTabListHandler( new ServerUnique() );
break; break;
} }
listeners = adapter.getListeners(); listeners = adapter.getListeners();
Preconditions.checkArgument( listeners != null && !listeners.isEmpty(), "No listeners defined." ); Preconditions.checkArgument( listeners != null && !listeners.isEmpty(), "No listeners defined." );
servers = adapter.getServers(); Map<String, ServerInfo> newServers = adapter.getServers();
Preconditions.checkArgument( servers != null && !servers.isEmpty(), "No servers defined" ); Preconditions.checkArgument( newServers != null && !newServers.isEmpty(), "No servers defined" );
if ( servers == null )
{
servers = newServers;
} else
{
for ( ServerInfo oldServer : servers.values() )
{
// Don't allow servers to be removed
Preconditions.checkArgument( newServers.containsValue( oldServer ), "Server %s removed on reload!", oldServer.getName() );
}
// Add new servers
for ( Map.Entry<String, ServerInfo> newServer : newServers.entrySet() )
{
if ( !servers.containsValue( newServer.getValue() ) )
{
servers.put( newServer.getKey(), newServer.getValue() );
}
}
}
for ( ListenerInfo listener : listeners ) for ( ListenerInfo listener : listeners )
{ {
Preconditions.checkArgument( servers.containsKey( listener.getDefaultServer() ) ); Preconditions.checkArgument( servers.containsKey( listener.getDefaultServer() ), "Default server %s is not defined", listener.getDefaultServer() );
} }
} }
} }

View File

@@ -20,6 +20,7 @@ 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 org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
@@ -151,8 +152,9 @@ 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 );
boolean restricted = get( "restricted", false, val );
InetSocketAddress address = Util.getAddr( addr ); InetSocketAddress address = Util.getAddr( addr );
ServerInfo info = ProxyServer.getInstance().constructServerInfo( name, address ); ServerInfo info = ProxyServer.getInstance().constructServerInfo( name, address, restricted );
ret.put( name, info ); ret.put( name, info );
} }
@@ -179,12 +181,16 @@ public class YamlConfig implements ConfigurationAdapter
int maxPlayers = get( "max_players", 1, val ); int maxPlayers = get( "max_players", 1, val );
String defaultServer = get( "default_server", "lobby", val ); String defaultServer = get( "default_server", "lobby", val );
String fallbackServer = get( "fallback_server", defaultServer, val );
boolean forceDefault = get( "force_default_server", false, val ); boolean forceDefault = get( "force_default_server", false, val );
String host = get( "host", "0.0.0.0:25577", val ); String host = get( "host", "0.0.0.0:25577", val );
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 = get( "forced_hosts", forcedDef, val ); Map<String, String> forced = get( "forced_hosts", forcedDef, val );
ListenerInfo info = new ListenerInfo( address, motd, maxPlayers, tabListSize, defaultServer, forceDefault, forced ); String textureURL = get( "texture_url", null, val );
int textureSize = get( "texture_size", 16, val );
TexturePackInfo texture = ( textureURL == null ) ? null : new TexturePackInfo( textureURL, textureSize );
ListenerInfo info = new ListenerInfo( address, motd, maxPlayers, tabListSize, defaultServer, fallbackServer, forceDefault, forced, texture );
ret.add( info ); ret.add( info );
} }

View File

@@ -0,0 +1,17 @@
package net.md_5.bungee.connection;
public class CancelSendSignal extends Error
{
@Override
public Throwable initCause(Throwable cause)
{
return this;
}
@Override
public Throwable fillInStackTrace()
{
return this;
}
}

View File

@@ -0,0 +1,349 @@
package net.md_5.bungee.connection;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import io.netty.channel.Channel;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.EntityMap;
import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerKickEvent;
import net.md_5.bungee.api.scoreboard.Objective;
import net.md_5.bungee.api.scoreboard.Position;
import net.md_5.bungee.api.scoreboard.Score;
import net.md_5.bungee.api.scoreboard.Scoreboard;
import net.md_5.bungee.api.scoreboard.Team;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.packet.Packet0KeepAlive;
import net.md_5.bungee.packet.Packet3Chat;
import net.md_5.bungee.packet.PacketC9PlayerListItem;
import net.md_5.bungee.packet.PacketCEScoreboardObjective;
import net.md_5.bungee.packet.PacketCFScoreboardScore;
import net.md_5.bungee.packet.PacketD0DisplayScoreboard;
import net.md_5.bungee.packet.PacketD1Team;
import net.md_5.bungee.packet.PacketFAPluginMessage;
import net.md_5.bungee.packet.PacketFFKick;
import net.md_5.bungee.packet.PacketHandler;
@RequiredArgsConstructor
public class DownstreamBridge extends PacketHandler
{
private final ProxyServer bungee;
private final UserConnection con;
private final ServerConnection server;
@Override
public void exception(Throwable t) throws Exception
{
ServerInfo def = bungee.getServerInfo( con.getPendingConnection().getListener().getFallbackServer() );
if ( server.getInfo() != def )
{
con.connectNow( def );
con.sendMessage( ChatColor.RED + "The server you were previously on went down, you have been connected to the lobby" );
} else
{
con.disconnect( Util.exception( t ) );
}
}
@Override
public void disconnected(ChannelWrapper channel) throws Exception
{
// We lost connection to the server
server.getInfo().removePlayer( con );
bungee.getReconnectHandler().setServer( con );
if ( !server.isObsolete() )
{
con.disconnect( "[Proxy] Lost connection to server D:" );
}
}
@Override
public void handle(byte[] buf) throws Exception
{
EntityMap.rewrite( buf, con.getServerEntityId(), con.getClientEntityId() );
con.sendPacket( buf );
}
@Override
public void handle(Packet0KeepAlive alive) throws Exception
{
con.setSentPingId( alive.id );
con.setSentPingTime( System.currentTimeMillis() );
}
@Override
public void handle(Packet3Chat chat) throws Exception
{
ChatEvent chatEvent = new ChatEvent( con.getServer(), con, chat.message );
bungee.getPluginManager().callEvent( chatEvent );
if ( chatEvent.isCancelled() )
{
throw new CancelSendSignal();
}
}
@Override
public void handle(PacketC9PlayerListItem playerList) throws Exception
{
if ( !bungee.getTabListHandler().onListUpdate( con, playerList.username, playerList.online, playerList.ping ) )
{
throw new CancelSendSignal();
}
}
@Override
public void handle(PacketCEScoreboardObjective objective) throws Exception
{
Scoreboard serverScoreboard = con.getServerSentScoreboard();
switch ( objective.action )
{
case 0:
serverScoreboard.addObjective( new Objective( objective.name, objective.text ) );
break;
case 1:
serverScoreboard.removeObjective( objective.name );
break;
}
}
@Override
public void handle(PacketCFScoreboardScore score) throws Exception
{
Scoreboard serverScoreboard = con.getServerSentScoreboard();
switch ( score.action )
{
case 0:
Score s = new Score( score.itemName, score.scoreName, score.value );
serverScoreboard.removeScore( score.itemName );
serverScoreboard.addScore( s );
break;
case 1:
serverScoreboard.removeScore( score.itemName );
break;
}
}
@Override
public void handle(PacketD0DisplayScoreboard displayScoreboard) throws Exception
{
Scoreboard serverScoreboard = con.getServerSentScoreboard();
serverScoreboard.setName( displayScoreboard.name );
serverScoreboard.setPosition( Position.values()[displayScoreboard.position] );
}
@Override
public void handle(PacketD1Team team) throws Exception
{
Scoreboard serverScoreboard = con.getServerSentScoreboard();
// Remove team and move on
if ( team.mode == 1 )
{
serverScoreboard.removeTeam( team.name );
return;
}
// Create or get old team
Team t;
if ( team.mode == 0 )
{
t = new Team( team.name );
serverScoreboard.addTeam( t );
} else
{
t = serverScoreboard.getTeam( team.name );
}
if ( t != null )
{
if ( team.mode == 0 || team.mode == 2 )
{
t.setDisplayName( team.displayName );
t.setPrefix( team.prefix );
t.setSuffix( team.suffix );
t.setFriendlyMode( team.friendlyFire );
}
if ( team.players != null )
{
for ( String s : team.players )
{
if ( team.mode == 0 || team.mode == 3 )
{
t.addPlayer( s );
} else
{
t.removePlayer( s );
}
}
}
}
}
@Override
public void handle(PacketFAPluginMessage pluginMessage) throws Exception
{
ByteArrayDataInput in = ByteStreams.newDataInput( pluginMessage.data );
PluginMessageEvent event = new PluginMessageEvent( con.getServer(), con, pluginMessage.tag, pluginMessage.data.clone() );
if ( bungee.getPluginManager().callEvent( event ).isCancelled() )
{
throw new CancelSendSignal();
}
if ( pluginMessage.tag.equals( "MC|TPack" ) && con.getPendingConnection().getListener().getTexturePack() != null )
{
throw new CancelSendSignal();
}
if ( pluginMessage.tag.equals( "BungeeCord" ) )
{
ByteArrayDataOutput out = ByteStreams.newDataOutput();
String subChannel = in.readUTF();
if ( subChannel.equals( "Forward" ) )
{
// Read data from server
String target = in.readUTF();
String channel = in.readUTF();
short len = in.readShort();
byte[] data = new byte[ len ];
in.readFully( data );
// Prepare new data to send
out.writeUTF( channel );
out.writeShort( data.length );
out.write( data );
byte[] payload = out.toByteArray();
// Null out stream, important as we don't want to send to ourselves
out = null;
if ( target.equals( "ALL" ) )
{
for ( ServerInfo server : bungee.getServers().values() )
{
if ( server != con.getServer().getInfo() )
{
server.sendData( "BungeeCord", payload );
}
}
} else
{
ServerInfo server = bungee.getServerInfo( target );
if ( server != null )
{
server.sendData( "BungeeCord", payload );
}
}
}
if ( subChannel.equals( "Connect" ) )
{
ServerInfo server = bungee.getServerInfo( in.readUTF() );
if ( server != null )
{
con.connect( server );
}
}
if ( subChannel.equals( "IP" ) )
{
out.writeUTF( "IP" );
out.writeUTF( con.getAddress().getHostString() );
out.writeInt( con.getAddress().getPort() );
}
if ( subChannel.equals( "PlayerCount" ) )
{
ServerInfo server = bungee.getServerInfo( in.readUTF() );
if ( server != null )
{
out.writeUTF( "PlayerCount" );
out.writeUTF( server.getName() );
out.writeInt( server.getPlayers().size() );
}
}
if ( subChannel.equals( "PlayerList" ) )
{
String target = in.readUTF();
out.writeUTF( "PlayerList" );
if ( target.equals( "ALL" ) )
{
out.writeUTF( Util.csv( bungee.getPlayers() ) );
} else
{
ServerInfo server = bungee.getServerInfo( target );
if ( server != null )
{
out.writeUTF( server.getName() );
out.writeUTF( Util.csv( server.getPlayers() ) );
}
}
}
if ( subChannel.equals( "GetServers" ) )
{
out.writeUTF( "GetServers" );
out.writeUTF( Util.csv( bungee.getServers().keySet() ) );
}
if ( subChannel.equals( "Message" ) )
{
ProxiedPlayer target = bungee.getPlayer( in.readUTF() );
if ( target != null )
{
target.sendMessage( in.readUTF() );
}
}
if ( subChannel.equals( "GetServer" ) )
{
out.writeUTF( "GetServer" );
out.writeUTF( server.getInfo().getName() );
}
// Check we haven't set out to null, and we have written data, if so reply back back along the BungeeCord channel
if ( out != null )
{
byte[] b = out.toByteArray();
if ( b.length != 0 )
{
con.getServer().sendData( "BungeeCord", b );
}
}
}
}
@Override
public void handle(PacketFFKick kick) throws Exception
{
ServerInfo def = bungee.getServerInfo( con.getPendingConnection().getListener().getFallbackServer() );
if ( Objects.equals( server.getInfo(), def ) )
{
def = null;
}
ServerKickEvent event = bungee.getPluginManager().callEvent( new ServerKickEvent( con, kick.message, def ) );
if ( event.isCancelled() && event.getCancelServer() != null )
{
con.connectNow( event.getCancelServer() );
} else
{
con.disconnect( "[Kicked] " + event.getKickReason() );
}
server.setObsolete( true );
throw new CancelSendSignal();
}
@Override
public String toString()
{
return "[" + con.getName() + "] <-> DownstreamBridge <-> [" + server.getInfo().getName() + "]";
}
}

View File

@@ -0,0 +1,267 @@
package net.md_5.bungee.connection;
import com.google.common.base.Preconditions;
import com.ning.http.client.AsyncCompletionHandler;
import com.ning.http.client.Response;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.EncryptionUtil;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.netty.CipherCodec;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.packet.Packet2Handshake;
import net.md_5.bungee.packet.PacketCDClientStatus;
import net.md_5.bungee.packet.PacketFAPluginMessage;
import net.md_5.bungee.packet.PacketFCEncryptionResponse;
import net.md_5.bungee.packet.PacketFDEncryptionRequest;
import net.md_5.bungee.packet.PacketFEPing;
import net.md_5.bungee.packet.PacketFFKick;
import net.md_5.bungee.packet.PacketHandler;
@RequiredArgsConstructor
public class InitialHandler extends PacketHandler implements PendingConnection
{
private final ProxyServer bungee;
private ChannelWrapper ch;
@Getter
private final ListenerInfo listener;
@Getter
private Packet2Handshake handshake;
private PacketFDEncryptionRequest request;
private List<PacketFAPluginMessage> loginMessages = new ArrayList<>();
private State thisState = State.HANDSHAKE;
private SecretKey sharedKey;
private boolean disconnected;
private enum State
{
HANDSHAKE, ENCRYPT, LOGIN, FINISHED;
}
@Override
public void connected(ChannelWrapper channel) throws Exception
{
this.ch = channel;
}
@Override
public void exception(Throwable t) throws Exception
{
disconnect( ChatColor.RED + Util.exception( t ) );
}
@Override
public void handle(PacketFAPluginMessage pluginMessage) throws Exception
{
loginMessages.add( pluginMessage );
}
@Override
public void handle(PacketFEPing ping) throws Exception
{
ServerPing response = new ServerPing( bungee.getProtocolVersion(), bungee.getGameVersion(),
listener.getMotd(), bungee.getPlayers().size(), listener.getMaxPlayers() );
response = bungee.getPluginManager().callEvent( new ProxyPingEvent( this, response ) ).getResponse();
String kickMessage = ChatColor.DARK_BLUE
+ "\00" + response.getProtocolVersion()
+ "\00" + response.getGameVersion()
+ "\00" + response.getMotd()
+ "\00" + response.getCurrentPlayers()
+ "\00" + response.getMaxPlayers();
disconnect( kickMessage );
}
@Override
public void handle(Packet2Handshake handshake) throws Exception
{
Preconditions.checkState( thisState == State.HANDSHAKE, "Not expecting HANDSHAKE" );
if ( handshake.username.length() > 16 )
{
disconnect( "Cannot have username longer than 16 characters" );
return;
}
int limit = BungeeCord.getInstance().config.getPlayerLimit();
Preconditions.checkState( limit <= 0 || bungee.getPlayers().size() < limit, "Server is full!" );
this.handshake = handshake;
ch.write( request = EncryptionUtil.encryptRequest() );
thisState = State.ENCRYPT;
}
@Override
public void handle(final PacketFCEncryptionResponse encryptResponse) throws Exception
{
Preconditions.checkState( thisState == State.ENCRYPT, "Not expecting ENCRYPT" );
sharedKey = EncryptionUtil.getSecret( encryptResponse, request );
if ( BungeeCord.getInstance().config.isOnlineMode() )
{
String encName = URLEncoder.encode( InitialHandler.this.getName(), "UTF-8" );
MessageDigest sha = MessageDigest.getInstance( "SHA-1" );
for ( byte[] bit : new byte[][]
{
request.serverId.getBytes( "ISO_8859_1" ), sharedKey.getEncoded(), EncryptionUtil.keys.getPublic().getEncoded()
} )
{
sha.update( bit );
}
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;
bungee.getHttpClient().prepareGet( authURL ).execute( new AsyncCompletionHandler<Response>()
{
@Override
public Response onCompleted(Response response) throws Exception
{
if ( "YES".equals( response.getResponseBody() ) )
{
finish();
} else
{
disconnect( "Not authenticated with Minecraft.net" );
}
return response;
}
@Override
public void onThrowable(Throwable t)
{
disconnect( "Error occured while contacting login servers, are they down?" + Util.exception( t ) );
}
} );
} else
{
finish();
}
}
private void finish() throws GeneralSecurityException
{
// Check for multiple connections
ProxiedPlayer old = bungee.getPlayer( handshake.username );
if ( old != null )
{
old.disconnect( "You are already connected to the server" );
}
Callback<LoginEvent> complete = new Callback<LoginEvent>()
{
@Override
public void done(LoginEvent result, Throwable error)
{
if ( result.isCancelled() )
{
disconnect( result.getCancelReason() );
}
if ( disconnected )
{
return;
}
try
{
Cipher encrypt = EncryptionUtil.getCipher( Cipher.ENCRYPT_MODE, sharedKey );
Cipher decrypt = EncryptionUtil.getCipher( Cipher.DECRYPT_MODE, sharedKey );
ch.write( new PacketFCEncryptionResponse() );
ch.getHandle().pipeline().addBefore( "decoder", "cipher", new CipherCodec( encrypt, decrypt ) );
thisState = InitialHandler.State.LOGIN;
} catch ( GeneralSecurityException ex )
{
disconnect( "Cipher error: " + Util.exception( ex ) );
}
}
};
// fire login event
bungee.getPluginManager().callEvent( new LoginEvent( InitialHandler.this, complete ) );
}
@Override
public void handle(PacketCDClientStatus clientStatus) throws Exception
{
Preconditions.checkState( thisState == State.LOGIN, "Not expecting LOGIN" );
UserConnection userCon = new UserConnection( (BungeeCord) bungee, ch,getName(), this );
userCon.init();
bungee.getPluginManager().callEvent( new PostLoginEvent( userCon ) );
ch.getHandle().pipeline().get( HandlerBoss.class ).setHandler( new UpstreamBridge( bungee, userCon ) );
ServerInfo server = bungee.getReconnectHandler().getServer( userCon );
userCon.connect( server, true );
thisState = State.FINISHED;
throw new CancelSendSignal();
}
@Override
public synchronized void disconnect(String reason)
{
if ( ch.getHandle().isActive() )
{
ch.write( new PacketFFKick( reason ) );
ch.getHandle().close();
disconnected = true;
}
}
@Override
public String getName()
{
return ( handshake == null ) ? null : handshake.username;
}
@Override
public byte getVersion()
{
return ( handshake == null ) ? -1 : handshake.procolVersion;
}
@Override
public InetSocketAddress getVirtualHost()
{
return ( handshake == null ) ? null : new InetSocketAddress( handshake.host, handshake.port );
}
@Override
public InetSocketAddress getAddress()
{
return (InetSocketAddress) ch.getHandle().remoteAddress();
}
@Override
public String toString()
{
return "[" + ( ( getName() != null ) ? getName() : getAddress() ) + "] <-> InitialHandler";
}
}

View File

@@ -0,0 +1,47 @@
package net.md_5.bungee.connection;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.packet.PacketFFKick;
import net.md_5.bungee.packet.PacketHandler;
@RequiredArgsConstructor
public class PingHandler extends PacketHandler
{
private final ServerInfo target;
private final Callback<ServerPing> callback;
private static final byte[] pingBuf = new byte[]
{
(byte) 0xFE, (byte) 0x01
};
@Override
public void connected(ChannelWrapper channel) throws Exception
{
channel.write( pingBuf );
}
@Override
public void exception(Throwable t) throws Exception
{
callback.done( null, t );
}
@Override
public void handle(PacketFFKick kick) throws Exception
{
String[] split = kick.message.split( "\00" );
ServerPing ping = new ServerPing( Byte.parseByte( split[1] ), split[2], split[3], Integer.parseInt( split[4] ), Integer.parseInt( split[5] ) );
callback.done( ping, null );
}
@Override
public String toString()
{
return "[Ping Handler] -> " + target.getName();
}
}

View File

@@ -0,0 +1,128 @@
package net.md_5.bungee.connection;
import io.netty.channel.Channel;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.EntityMap;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.TexturePackInfo;
import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.packet.Packet0KeepAlive;
import net.md_5.bungee.packet.Packet3Chat;
import net.md_5.bungee.packet.PacketCCSettings;
import net.md_5.bungee.packet.PacketFAPluginMessage;
import net.md_5.bungee.packet.PacketHandler;
public class UpstreamBridge extends PacketHandler
{
private final ProxyServer bungee;
private final UserConnection con;
public UpstreamBridge(ProxyServer bungee, UserConnection con)
{
this.bungee = bungee;
this.con = con;
BungeeCord.getInstance().connections.put( con.getName(), con );
bungee.getTabListHandler().onConnect( con );
con.sendPacket( BungeeCord.getInstance().registerChannels() );
TexturePackInfo texture = con.getPendingConnection().getListener().getTexturePack();
if ( texture != null )
{
con.sendPacket( new PacketFAPluginMessage( "MC|TPack", ( texture.getUrl() + "\00" + texture.getSize() ).getBytes() ) );
}
}
@Override
public void exception(Throwable t) throws Exception
{
con.disconnect( Util.exception( t ) );
}
@Override
public void disconnected(ChannelWrapper channel) throws Exception
{
// We lost connection to the client
PlayerDisconnectEvent event = new PlayerDisconnectEvent( con );
bungee.getPluginManager().callEvent( event );
bungee.getTabListHandler().onDisconnect( con );
BungeeCord.getInstance().connections.remove( con.getName() ); //TODO: Better way, why do we need to raw access?
if ( con.getServer() != null )
{
con.getServer().disconnect( "Quitting" );
}
}
@Override
public void handle(byte[] buf) throws Exception
{
EntityMap.rewrite( buf, con.getClientEntityId(), con.getServerEntityId() );
if ( con.getServer() != null )
{
con.getServer().getCh().write( buf );
}
}
@Override
public void handle(Packet0KeepAlive alive) throws Exception
{
if ( alive.id == con.getSentPingId() )
{
int newPing = (int) ( System.currentTimeMillis() - con.getSentPingTime() );
bungee.getTabListHandler().onPingChange( con, newPing );
con.setPing( newPing );
}
}
@Override
public void handle(Packet3Chat chat) throws Exception
{
ChatEvent chatEvent = new ChatEvent( con, con.getServer(), chat.message );
if ( bungee.getPluginManager().callEvent( chatEvent ).isCancelled() )
{
throw new CancelSendSignal();
}
if ( chatEvent.isCommand() )
{
if ( bungee.getPluginManager().dispatchCommand( con, chat.message.substring( 1 ) ) )
{
throw new CancelSendSignal();
}
}
}
@Override
public void handle(PacketCCSettings settings) throws Exception
{
con.setSettings( settings );
}
@Override
public void handle(PacketFAPluginMessage pluginMessage) throws Exception
{
if ( pluginMessage.tag.equals( "BungeeCord" ) )
{
throw new CancelSendSignal();
}
PluginMessageEvent event = new PluginMessageEvent( con, con.getServer(), pluginMessage.tag, pluginMessage.data.clone() );
if ( bungee.getPluginManager().callEvent( event ).isCancelled() )
{
throw new CancelSendSignal();
}
}
@Override
public String toString()
{
return "[" + con.getName() + "] -> UpstreamBridge";
}
}

View File

@@ -0,0 +1,26 @@
package net.md_5.bungee.netty;
import io.netty.channel.Channel;
public class ChannelWrapper
{
private final Channel ch;
private final ReusableChannelPromise promise;
public ChannelWrapper(Channel ch)
{
this.ch = ch;
this.promise = new ReusableChannelPromise( ch );
}
public void write(Object packet)
{
ch.write( packet, promise );
}
public Channel getHandle()
{
return ch;
}
}

View File

@@ -0,0 +1,68 @@
package net.md_5.bungee.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToByteCodec;
import javax.crypto.Cipher;
import javax.crypto.ShortBufferException;
/**
* This class is a complete solution for encrypting and decoding bytes in a
* Netty stream. It takes two {@link Cipher} instances, used for encryption and
* decryption respectively.
*/
public class CipherCodec extends ByteToByteCodec
{
private Cipher encrypt;
private Cipher decrypt;
private ThreadLocal<byte[]> heapInLocal = new EmptyByteThreadLocal();
private ThreadLocal<byte[]> heapOutLocal = new EmptyByteThreadLocal();
private static class EmptyByteThreadLocal extends ThreadLocal<byte[]>
{
@Override
protected byte[] initialValue()
{
return new byte[ 0 ];
}
}
public CipherCodec(Cipher encrypt, Cipher decrypt)
{
this.encrypt = encrypt;
this.decrypt = decrypt;
}
@Override
public void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception
{
cipher( in, out, encrypt );
}
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception
{
cipher( in, out, decrypt );
}
private void cipher(ByteBuf in, ByteBuf out, Cipher cipher) throws ShortBufferException
{
byte[] heapIn = heapInLocal.get();
int readableBytes = in.readableBytes();
if ( heapIn.length < readableBytes )
{
heapIn = new byte[ readableBytes ];
}
in.readBytes( heapIn, 0, readableBytes );
byte[] heapOut = heapOutLocal.get();
int outputSize = cipher.getOutputSize( readableBytes );
if ( heapOut.length < outputSize )
{
heapOut = new byte[ outputSize ];
}
out.writeBytes( heapOut, 0, cipher.update( heapIn, 0, readableBytes, heapOut ) );
}
}

View File

@@ -0,0 +1,18 @@
package net.md_5.bungee.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import net.md_5.bungee.packet.DefinedPacket;
@ChannelHandler.Sharable
public class DefinedPacketEncoder extends MessageToByteEncoder<DefinedPacket>
{
@Override
protected void encode(ChannelHandlerContext ctx, DefinedPacket msg, ByteBuf out) throws Exception
{
out.writeBytes( msg.getPacket() );
}
}

View File

@@ -0,0 +1,104 @@
package net.md_5.bungee.netty;
import com.google.common.base.Preconditions;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundMessageHandlerAdapter;
import io.netty.handler.timeout.ReadTimeoutException;
import java.io.IOException;
import java.util.logging.Level;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.connection.CancelSendSignal;
import net.md_5.bungee.packet.DefinedPacket;
import net.md_5.bungee.packet.PacketHandler;
/**
* This class is a primitive wrapper for {@link PacketHandler} instances tied to
* channels to maintain simple states, and only call the required, adapted
* methods when the channel is connected.
*/
public class HandlerBoss extends ChannelInboundMessageHandlerAdapter<byte[]>
{
private ChannelWrapper channel;
private PacketHandler handler;
public void setHandler(PacketHandler handler)
{
Preconditions.checkArgument( handler != null, "handler" );
this.handler = handler;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception
{
if ( handler != null )
{
channel = new ChannelWrapper( ctx.channel() );
handler.connected( channel );
ProxyServer.getInstance().getLogger().log( Level.INFO, "{0} has connected", handler );
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception
{
if ( handler != null )
{
ProxyServer.getInstance().getLogger().log( Level.INFO, "{0} has disconnected", handler );
handler.disconnected( channel );
}
}
@Override
public void messageReceived(ChannelHandlerContext ctx, byte[] msg) throws Exception
{
if ( handler != null && ctx.channel().isActive() )
{
DefinedPacket packet = DefinedPacket.packet( msg );
boolean sendPacket = true;
if ( packet != null )
{
try
{
packet.handle( handler );
} catch ( CancelSendSignal ex )
{
sendPacket = false;
}
}
if ( sendPacket )
{
handler.handle( msg );
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
if ( ctx.channel().isActive() )
{
if ( cause instanceof ReadTimeoutException )
{
ProxyServer.getInstance().getLogger().log( Level.WARNING, handler + " - read timed out" );
} else if ( cause instanceof IOException )
{
ProxyServer.getInstance().getLogger().log( Level.WARNING, handler + " - IOException: " + cause.getMessage() );
} else
{
ProxyServer.getInstance().getLogger().log( Level.SEVERE, handler + " - encountered exception", cause );
}
if ( handler != null )
{
try
{
handler.exception( cause );
} catch ( Exception ex )
{
ProxyServer.getInstance().getLogger().log( Level.SEVERE, handler + " - exception processing exception", ex );
}
}
ctx.close();
}
}
}

View File

@@ -0,0 +1,37 @@
package net.md_5.bungee.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import net.md_5.bungee.protocol.netty.PacketReader;
/**
* This class will attempt to read a packet from {@link PacketReader}, with the
* specified {@link #protocol} before returning a new {@link ByteBuf} with the
* copied contents of all bytes read in this frame.
* <p/>
* It is based on {@link ReplayingDecoder} so that packets will only be returned
* when all needed data is present.
*/
@AllArgsConstructor
public class PacketDecoder extends ReplayingDecoder<Void>
{
@Getter
@Setter
private int protocol;
@Override
protected byte[] decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception
{
int startIndex = in.readerIndex();
PacketReader.readPacket( in, protocol );
byte[] buf = new byte[ in.readerIndex() - startIndex ];
in.readerIndex( startIndex );
in.readBytes( buf, 0, buf.length );
return buf;
}
}

View File

@@ -0,0 +1,68 @@
package net.md_5.bungee.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.AttributeKey;
import java.util.concurrent.TimeUnit;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.BungeeServerInfo;
import net.md_5.bungee.ServerConnector;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.protocol.PacketDefinitions;
public class PipelineUtils
{
public static final AttributeKey<ListenerInfo> LISTENER = new AttributeKey<>( "ListerInfo" );
public static final AttributeKey<UserConnection> USER = new AttributeKey<>( "User" );
public static final AttributeKey<BungeeServerInfo> TARGET = new AttributeKey<>( "Target" );
public static final ChannelInitializer<Channel> SERVER_CHILD = new ChannelInitializer<Channel>()
{
@Override
protected void initChannel(Channel ch) throws Exception
{
BASE.initChannel( ch );
ch.pipeline().get( HandlerBoss.class ).setHandler( new InitialHandler( ProxyServer.getInstance(), ch.attr( LISTENER ).get() ) );
}
};
public static final ChannelInitializer<Channel> CLIENT = new ChannelInitializer<Channel>()
{
@Override
protected void initChannel(Channel ch) throws Exception
{
BASE.initChannel( ch );
ch.pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( ProxyServer.getInstance(), ch.attr( USER ).get(), ch.attr( TARGET ).get() ) );
}
};
public static final Base BASE = new Base();
private static final DefinedPacketEncoder packetEncoder = new DefinedPacketEncoder();
private static final ByteArrayEncoder arrayEncoder = new ByteArrayEncoder();
public final static class Base extends ChannelInitializer<Channel>
{
@Override
public void initChannel(Channel ch) throws Exception
{
try
{
ch.config().setOption( ChannelOption.IP_TOS, 0x18 );
} catch ( ChannelException ex )
{
// IP_TOS is not supported (Windows XP / Windows Server 2003)
}
ch.pipeline().addLast( "timer", new ReadTimeoutHandler( BungeeCord.getInstance().config.getTimeout(), TimeUnit.MILLISECONDS ) );
ch.pipeline().addLast( "decoder", new PacketDecoder( PacketDefinitions.VANILLA_PROTOCOL ) );
ch.pipeline().addLast( "packet-encoder", packetEncoder );
ch.pipeline().addLast( "array-encoder", arrayEncoder );
ch.pipeline().addLast( "handler", new HandlerBoss() );
}
};
}

View File

@@ -0,0 +1,179 @@
package net.md_5.bungee.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class ReusableChannelPromise implements ChannelPromise
{
private final Channel ch;
@Override
public Channel channel()
{
return ch;
}
@Override
public ChannelPromise setSuccess(Void result)
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public ChannelPromise setSuccess()
{
return this;
}
@Override
public boolean trySuccess()
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public ChannelPromise setFailure(Throwable cause)
{
return this;
}
@Override
public ChannelPromise addListener(GenericFutureListener<? extends Future<Void>> listener)
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public ChannelPromise addListeners(GenericFutureListener<? extends Future<Void>>... listeners)
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public ChannelPromise removeListener(GenericFutureListener<? extends Future<Void>> listener)
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public ChannelPromise removeListeners(GenericFutureListener<? extends Future<Void>>... listeners)
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public ChannelPromise sync() throws InterruptedException
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public ChannelPromise syncUninterruptibly()
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public ChannelPromise await() throws InterruptedException
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public ChannelPromise awaitUninterruptibly()
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public boolean isSuccess()
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public Throwable cause()
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public boolean await(long timeoutMillis) throws InterruptedException
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public boolean awaitUninterruptibly(long timeout, TimeUnit unit)
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public boolean awaitUninterruptibly(long timeoutMillis)
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public Void getNow()
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public boolean cancel(boolean mayInterruptIfRunning)
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public boolean isCancelled()
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public boolean isDone()
{
return false;
}
@Override
public Void get() throws InterruptedException, ExecutionException
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public boolean trySuccess(Void result)
{
throw new UnsupportedOperationException( "Not supported yet." );
}
@Override
public boolean tryFailure(Throwable cause)
{
throw new UnsupportedOperationException( "Not supported yet." );
}
}

View File

@@ -1,6 +1,5 @@
package net.md_5.bungee.packet; package net.md_5.bungee.packet;
import com.google.common.base.Preconditions;
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.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@@ -20,42 +19,33 @@ import net.md_5.bungee.Util;
public abstract class DefinedPacket implements DataOutput public abstract class DefinedPacket implements DataOutput
{ {
private interface Overriden private static interface Overriden
{ {
void readUTF(); void readUTF();
void writeUTF(String s); void writeUTF(String s);
} }
private ByteArrayInputStream bin; private ByteArrayInputStream byteStream;
private DataInputStream input; private DataInputStream in;
@Delegate(excludes = Overriden.class) @Delegate(excludes = Overriden.class)
private ByteArrayDataOutput out; private ByteArrayDataOutput out;
/** private byte[] buf;
* Packet id.
*/
public final int id;
/**
* Already constructed packet.
*/
private byte[] packet;
public DefinedPacket(int id, byte[] buf) public DefinedPacket(int id, byte[] buf)
{ {
bin = new ByteArrayInputStream( buf ); byteStream = new ByteArrayInputStream( buf );
input = new DataInputStream( bin ); in = new DataInputStream( byteStream );
if ( readUnsignedByte() != id ) if ( readUnsignedByte() != id )
{ {
throw new IllegalArgumentException( "Wasn't expecting packet id " + Util.hex( id ) ); throw new IllegalArgumentException( "Wasn't expecting packet id " + Util.hex( id ) );
} }
this.id = id; this.buf = buf;
packet = buf;
} }
public DefinedPacket(int id) public DefinedPacket(int id)
{ {
out = ByteStreams.newDataOutput(); out = ByteStreams.newDataOutput();
this.id = id;
writeByte( id ); writeByte( id );
} }
@@ -67,7 +57,7 @@ public abstract class DefinedPacket implements DataOutput
*/ */
public byte[] getPacket() public byte[] getPacket()
{ {
return packet == null ? packet = out.toByteArray() : packet; return buf == null ? buf = out.toByteArray() : buf;
} }
@Override @Override
@@ -104,14 +94,14 @@ public abstract class DefinedPacket implements DataOutput
public final int available() public final int available()
{ {
return bin.available(); return byteStream.available();
} }
public final void readFully(byte b[]) public final void readFully(byte b[])
{ {
try try
{ {
input.readFully( b ); in.readFully( b );
} catch ( IOException e ) } catch ( IOException e )
{ {
throw new IllegalStateException( e ); throw new IllegalStateException( e );
@@ -122,7 +112,7 @@ public abstract class DefinedPacket implements DataOutput
{ {
try try
{ {
return input.readBoolean(); return in.readBoolean();
} catch ( IOException e ) } catch ( IOException e )
{ {
throw new IllegalStateException( e ); throw new IllegalStateException( e );
@@ -133,7 +123,7 @@ public abstract class DefinedPacket implements DataOutput
{ {
try try
{ {
return input.readByte(); return in.readByte();
} catch ( IOException e ) } catch ( IOException e )
{ {
throw new IllegalStateException( e ); throw new IllegalStateException( e );
@@ -144,7 +134,7 @@ public abstract class DefinedPacket implements DataOutput
{ {
try try
{ {
return input.readUnsignedByte(); return in.readUnsignedByte();
} catch ( IOException e ) } catch ( IOException e )
{ {
throw new IllegalStateException( e ); throw new IllegalStateException( e );
@@ -155,7 +145,7 @@ public abstract class DefinedPacket implements DataOutput
{ {
try try
{ {
return input.readShort(); return in.readShort();
} catch ( IOException e ) } catch ( IOException e )
{ {
throw new IllegalStateException( e ); throw new IllegalStateException( e );
@@ -166,7 +156,7 @@ public abstract class DefinedPacket implements DataOutput
{ {
try try
{ {
return input.readChar(); return in.readChar();
} catch ( IOException e ) } catch ( IOException e )
{ {
throw new IllegalStateException( e ); throw new IllegalStateException( e );
@@ -177,7 +167,7 @@ public abstract class DefinedPacket implements DataOutput
{ {
try try
{ {
return input.readInt(); return in.readInt();
} catch ( IOException e ) } catch ( IOException e )
{ {
throw new IllegalStateException( e ); throw new IllegalStateException( e );
@@ -194,18 +184,27 @@ public abstract class DefinedPacket implements DataOutput
public abstract String toString(); public abstract String toString();
public abstract void handle(PacketHandler handler) throws Exception; public abstract void handle(PacketHandler handler) throws Exception;
@SuppressWarnings("unchecked")
private static Class<? extends DefinedPacket>[] classes = new Class[ 256 ]; private static Class<? extends DefinedPacket>[] classes = new Class[ 256 ];
@SuppressWarnings("unchecked")
private static Constructor<? extends DefinedPacket>[] consructors = new Constructor[ 256 ];
public static DefinedPacket packet(byte[] buf) public static DefinedPacket packet(byte[] buf)
{ {
int id = Util.getId( buf ); int id = buf[0] & 0xFF;
Class<? extends DefinedPacket> clazz = classes[id]; Class<? extends DefinedPacket> clazz = classes[id];
DefinedPacket ret = null; DefinedPacket ret = null;
if ( clazz != null ) if ( clazz != null )
{ {
try try
{ {
Constructor<? extends DefinedPacket> constructor = clazz.getDeclaredConstructor( byte[].class ); Constructor<? extends DefinedPacket> constructor = consructors[id];
if ( constructor == null )
{
constructor = clazz.getDeclaredConstructor( byte[].class );
consructors[id] = constructor;
}
if ( constructor != null ) if ( constructor != null )
{ {
ret = constructor.newInstance( buf ); ret = constructor.newInstance( buf );
@@ -215,7 +214,6 @@ public abstract class DefinedPacket implements DataOutput
} }
} }
Preconditions.checkState( ret != null, "Don't know how to deal with packet ID %s", Util.hex( id ) );
return ret; return ret;
} }
@@ -227,7 +225,12 @@ public abstract class DefinedPacket implements DataOutput
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[0xCC] = PacketCCSettings.class;
classes[0xCD] = PacketCDClientStatus.class; classes[0xCD] = PacketCDClientStatus.class;
classes[0xCE] = PacketCEScoreboardObjective.class;
classes[0xCF] = PacketCFScoreboardScore.class;
classes[0xD0] = PacketD0DisplayScoreboard.class;
classes[0xD1] = PacketD1Team.class;
classes[0xFA] = PacketFAPluginMessage.class; classes[0xFA] = PacketFAPluginMessage.class;
classes[0xFC] = PacketFCEncryptionResponse.class; classes[0xFC] = PacketFCEncryptionResponse.class;
classes[0xFD] = PacketFDEncryptionRequest.class; classes[0xFD] = PacketFDEncryptionRequest.class;

View File

@@ -10,9 +10,9 @@ public class Packet0KeepAlive extends DefinedPacket
public int id; public int id;
public Packet0KeepAlive(byte[] buffer) Packet0KeepAlive(byte[] buf)
{ {
super( 0x00, buffer ); super( 0x00, buf );
id = readInt(); id = readInt();
} }

View File

@@ -26,9 +26,16 @@ public class Packet1Login extends DefinedPacket
writeByte( difficulty ); writeByte( difficulty );
writeByte( unused ); writeByte( unused );
writeByte( maxPlayers ); writeByte( maxPlayers );
this.entityId = entityId;
this.levelType = levelType;
this.gameMode = gameMode;
this.dimension = dimension;
this.difficulty = difficulty;
this.unused = unused;
this.maxPlayers = maxPlayers;
} }
public Packet1Login(byte[] buf) Packet1Login(byte[] buf)
{ {
super( 0x01, buf ); super( 0x01, buf );
this.entityId = readInt(); this.entityId = readInt();

View File

@@ -20,9 +20,13 @@ public class Packet2Handshake extends DefinedPacket
writeUTF( username ); writeUTF( username );
writeUTF( host ); writeUTF( host );
writeInt( port ); writeInt( port );
this.procolVersion = protocolVersion;
this.username = username;
this.host = host;
this.port = port;
} }
public Packet2Handshake(byte[] buf) Packet2Handshake(byte[] buf)
{ {
super( 0x02, buf ); super( 0x02, buf );
this.procolVersion = readByte(); this.procolVersion = readByte();

View File

@@ -14,9 +14,10 @@ public class Packet3Chat extends DefinedPacket
{ {
super( 0x03 ); super( 0x03 );
writeUTF( message ); writeUTF( message );
this.message = message;
} }
public Packet3Chat(byte[] buf) Packet3Chat(byte[] buf)
{ {
super( 0x03, buf ); super( 0x03, buf );
this.message = readUTF(); this.message = readUTF();

View File

@@ -8,6 +8,8 @@ import lombok.ToString;
public class Packet9Respawn extends DefinedPacket public class Packet9Respawn extends DefinedPacket
{ {
public static final Packet9Respawn DIM1_SWITCH = new Packet9Respawn( (byte) 1, (byte) 0, (byte) 0, (short) 256, "DEFAULT" );
public static final Packet9Respawn DIM2_SWITCH = new Packet9Respawn( (byte) -1, (byte) 0, (byte) 0, (short) 256, "DEFAULT" );
public int dimension; public int dimension;
public byte difficulty; public byte difficulty;
public byte gameMode; public byte gameMode;
@@ -22,9 +24,14 @@ public class Packet9Respawn extends DefinedPacket
writeByte( gameMode ); writeByte( gameMode );
writeShort( worldHeight ); writeShort( worldHeight );
writeUTF( levelType ); writeUTF( levelType );
this.dimension = dimension;
this.difficulty = difficulty;
this.gameMode = gameMode;
this.worldHeight = worldHeight;
this.levelType = levelType;
} }
public Packet9Respawn(byte[] buf) Packet9Respawn(byte[] buf)
{ {
super( 0x09, buf ); super( 0x09, buf );
this.dimension = readInt(); this.dimension = readInt();

View File

@@ -12,9 +12,9 @@ public class PacketC9PlayerListItem extends DefinedPacket
public boolean online; public boolean online;
public int ping; public int ping;
public PacketC9PlayerListItem(byte[] packet) PacketC9PlayerListItem(byte[] buf)
{ {
super( 0xC9, packet ); super( 0xC9, buf );
username = readUTF(); username = readUTF();
online = readBoolean(); online = readBoolean();
ping = readShort(); ping = readShort();

View File

@@ -0,0 +1,32 @@
package net.md_5.bungee.packet;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@ToString
@EqualsAndHashCode(callSuper = false)
public class PacketCCSettings extends DefinedPacket
{
public String locale;
public byte viewDistance;
public byte chatFlags;
public byte difficulty;
public boolean showCape;
public PacketCCSettings(byte[] buf)
{
super( 0xCC, buf );
locale = readUTF();
viewDistance = readByte();
chatFlags = readByte();
difficulty = readByte();
showCape = readBoolean();
}
@Override
public void handle(PacketHandler handler) throws Exception
{
handler.handle( this );
}
}

View File

@@ -8,6 +8,10 @@ import lombok.ToString;
public class PacketCDClientStatus extends DefinedPacket public class PacketCDClientStatus extends DefinedPacket
{ {
/**
* Represents the packet the client sends to the server when it is ready to
* login.
*/
public static PacketCDClientStatus CLIENT_LOGIN = new PacketCDClientStatus( (byte) 0 ); public static PacketCDClientStatus CLIENT_LOGIN = new PacketCDClientStatus( (byte) 0 );
/** /**
@@ -21,7 +25,7 @@ public class PacketCDClientStatus extends DefinedPacket
writeByte( payload ); writeByte( payload );
} }
public PacketCDClientStatus(byte[] buf) PacketCDClientStatus(byte[] buf)
{ {
super( 0xCD, buf ); super( 0xCD, buf );
} }

View File

@@ -0,0 +1,42 @@
package net.md_5.bungee.packet;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@ToString
@EqualsAndHashCode(callSuper = false)
public class PacketCEScoreboardObjective extends DefinedPacket
{
public String name;
public String text;
/**
* 0 to create, 1 to remove.
*/
public byte action;
public PacketCEScoreboardObjective(String name, String text, byte status)
{
super( 0xCE );
writeUTF( name );
writeUTF( text );
writeByte( status );
this.name = name;
this.text = text;
this.action = status;
}
PacketCEScoreboardObjective(byte[] buf)
{
super( 0xCE, buf );
this.name = readUTF();
this.text = readUTF();
this.action = readByte();
}
@Override
public void handle(PacketHandler handler) throws Exception
{
handler.handle( this );
}
}

View File

@@ -0,0 +1,52 @@
package net.md_5.bungee.packet;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@ToString
@EqualsAndHashCode(callSuper = false)
public class PacketCFScoreboardScore extends DefinedPacket
{
public String itemName;
/**
* 0 = create / update, 1 = remove.
*/
public byte action;
public String scoreName;
public int value;
public PacketCFScoreboardScore(byte[] buf)
{
super( 0xCF, buf );
itemName = readUTF();
action = readByte();
if ( action == 0 )
{
scoreName = readUTF();
value = readInt();
}
}
public PacketCFScoreboardScore(String itemName, byte action, String scoreName, int value)
{
super( 0xCF );
writeUTF( itemName );
writeByte( action );
if ( action == 0 )
{
writeUTF( scoreName );
writeInt( value );
}
this.itemName = itemName;
this.action = action;
this.scoreName = scoreName;
this.value = value;
}
@Override
public void handle(PacketHandler handler) throws Exception
{
handler.handle( this );
}
}

View File

@@ -0,0 +1,29 @@
package net.md_5.bungee.packet;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@ToString
@EqualsAndHashCode(callSuper = false)
public class PacketD0DisplayScoreboard extends DefinedPacket
{
/**
* 0 = list, 1 = side, 2 = below.
*/
public byte position;
public String name;
public PacketD0DisplayScoreboard(byte[] buf)
{
super( 0xD0, buf );
position = readByte();
name = readUTF();
}
@Override
public void handle(PacketHandler handler) throws Exception
{
handler.handle( this );
}
}

View File

@@ -0,0 +1,63 @@
package net.md_5.bungee.packet;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@ToString
@EqualsAndHashCode(callSuper = false)
public class PacketD1Team extends DefinedPacket
{
public String name;
/**
* 0 - create, 1 remove, 2 info update, 3 player add, 4 player remove.
*/
public byte mode;
public String displayName;
public String prefix;
public String suffix;
public byte friendlyFire;
public short playerCount;
public String[] players;
public PacketD1Team(byte[] buf)
{
super( 0xD1, buf );
name = readUTF();
mode = readByte();
if ( mode == 0 || mode == 2 )
{
displayName = readUTF();
prefix = readUTF();
suffix = readUTF();
friendlyFire = readByte();
}
if ( mode == 0 || mode == 3 || mode == 4 )
{
players = new String[ readShort() ];
for ( int i = 0; i < players.length; i++ )
{
players[i] = readUTF();
}
}
}
public PacketD1Team()
{
super( 0xD1 );
}
public static PacketD1Team destroy(String name)
{
PacketD1Team packet = new PacketD1Team();
packet.writeUTF( name );
packet.writeByte( 1 );
return packet;
}
@Override
public void handle(PacketHandler handler) throws Exception
{
handler.handle( this );
}
}

View File

@@ -20,7 +20,7 @@ public class PacketFAPluginMessage extends DefinedPacket
this.data = data; this.data = data;
} }
public PacketFAPluginMessage(byte[] buf) PacketFAPluginMessage(byte[] buf)
{ {
super( 0xFA, buf ); super( 0xFA, buf );
this.tag = readUTF(); this.tag = readUTF();

View File

@@ -23,9 +23,11 @@ public class PacketFCEncryptionResponse extends DefinedPacket
super( 0xFC ); super( 0xFC );
writeArray( sharedSecret ); writeArray( sharedSecret );
writeArray( verifyToken ); writeArray( verifyToken );
this.sharedSecret = sharedSecret;
this.verifyToken = verifyToken;
} }
public PacketFCEncryptionResponse(byte[] buf) PacketFCEncryptionResponse(byte[] buf)
{ {
super( 0xFC, buf ); super( 0xFC, buf );
this.sharedSecret = readArray(); this.sharedSecret = readArray();

View File

@@ -1,5 +1,6 @@
package net.md_5.bungee.packet; package net.md_5.bungee.packet;
import io.netty.buffer.ByteBuf;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
@@ -23,7 +24,7 @@ public class PacketFDEncryptionRequest extends DefinedPacket
this.verifyToken = verifyToken; this.verifyToken = verifyToken;
} }
public PacketFDEncryptionRequest(byte[] buf) PacketFDEncryptionRequest(byte[] buf)
{ {
super( 0xFD, buf ); super( 0xFD, buf );
serverId = readUTF(); serverId = readUTF();

Some files were not shown because too many files have changed in this diff Show More