Compare commits

...
948 Commits
Author SHA1 Message Date
Dan Mulloy 11cb603130 Update version to 3.6.5 for release 2016-01-30 12:54:33 -05:00
Dan Mulloy 0dd20f8d08 Add tests for the updaters, improve the check command 2016-01-30 12:33:16 -05:00
Dan Mulloy 7630007833 Cut down on static calls to the main class 2016-01-29 23:41:26 -05:00
Dan Mulloy de8bb2bd66 Switch to HTTPS for Spigot update checking
Should fix the 403 errors
2016-01-29 23:28:10 -05:00
Dan Mulloy 83c2943b48 Update release version in the readme
Closes https://github.com/aadnk/ProtocolLib/pull/98
2016-01-23 19:46:49 -05:00
Dan Mulloy c368251954 Add some more tests for WrappedGameProfile 2016-01-10 15:52:35 -05:00
Dan Mulloy 39998eedf8 Make sure the plugins folder actually exists before iterating 2016-01-09 12:31:20 -05:00
Dan Mulloy 8e8cf54ddd Fix conflict checking with different plugins folders
Fixes #136
2016-01-09 12:28:05 -05:00
Dan Mulloy 5a5cd77bac Temporarily restore Java 6 compatibility
3.6.5 will be the last ProtocolLib release supporting Java 6
2015-11-30 19:03:32 -05:00
Dan Mulloy 125834205a Add md_5's repo for 1.7.10 2015-11-27 18:05:33 -05:00
Dan Mulloy 0c705ef59d Restore backwards compatibility with server pings
I don't know why people are using 1.8.1, but whatever
2015-11-27 17:02:41 -05:00
Dan Mulloy b17af68716 Clean up some less-than-conventional code 2015-11-24 15:35:23 -05:00
Dan Mulloy 3e69dd69ed Actually commit the files
I need to get eclipse to do this automatically
2015-11-14 14:32:51 -05:00
Dan Mulloy 395d77e721 Reimplement auto updater, fix legacy packets
Also a few minor bug fixes and improvements
Fixes #127
2015-11-14 14:31:45 -05:00
Dan Mulloy e84fca699d Whoops, left some debug info in 2015-11-13 20:07:12 -05:00
Dan Mulloy 880cf29fc7 Don't depend on packet IDs anymore for 1.9
Packet IDs will supposedly be shuffled in 1.9, so they are no longer
reliable for our packet associations. This shouldn't affect plugins,
unless you're using IDs, in which case you should stop doing that.

Addresses #128
2015-11-13 20:05:53 -05:00
Dan Mulloy 7dff86cb48 Fix the StreamSerializer
String-based methods will still work, but deserializing from a data
input stream has been deprecated since it depends on hacky code

Fixes #31, Fixes #125
2015-11-02 23:56:41 -05:00
Dan Mulloy 4bb917e596 Stop testing with open jdk 6
Build errors galore
2015-10-31 13:53:31 -04:00
Dan Mulloy bd28035024 Don't load classes when we're checking for them
This causes a ton of lag and doesn't seem to ever work
Addresses #124
2015-10-31 13:47:45 -04:00
Dan Mulloy c40d9c1e6c Add some debug info for issue #109 2015-10-31 13:12:18 -04:00
Dan Mulloy cd7894f9f1 Ignore improperly registered custom packets
Fixes #123
2015-10-23 20:28:49 -04:00
Dan Mulloy 245433b29a Fix server pings and the infamous closed channel exception
As a result, packet listeners for OUT_SERVER_INFO will be processed on
the netty server io thread, so they will have to be thread safe if they
aren't already.
Fixes #119
2015-10-16 22:36:01 -04:00
Dan Mulloy 9433ea5e48 Add some debug info for exception caught messages
Also added support for deprecated block id's and suppressed some
compiler warnings with Java 7.
2015-10-07 18:12:50 -04:00
Dan Mulloy 0d3867c6f1 Add WrappedBlockData.getData(), improve block data test 2015-09-23 20:18:04 -04:00
Dan Mulloy 4e16792450 Delay server channel injection if late bind is detected
Fixes #116
2015-09-22 21:48:55 -04:00
Dan Mulloy a2452cba67 Add BlockPosition#toLocation 2015-08-29 12:42:47 -04:00
Dan Mulloy fc28a9242c Blacklist the UPDATE_ATTRIBUTES packet in the deep clone test 2015-08-21 17:52:20 -04:00
Dan Mulloy 368ed76677 Fix IllegalArgumentException with watchable objects 2015-08-09 18:27:00 -04:00
Dan Mulloy e6c87129bd Add version to stack traces, fix EnumWrappers in pre-1.8 2015-08-09 12:53:27 -04:00
Dan Mulloy 3166241397 Improve server mocking, test lore and material data 2015-07-29 12:29:59 -04:00
Dan Mulloy 8576b7a057 Update to 1.8.8 2015-07-27 15:05:32 -04:00
Dan Mulloy 0b63ae0354 Remove unnecessary method call 2015-07-24 12:40:47 -04:00
Dan Mulloy 0159752687 Fix NoSuchMethodError with pre-1.7.9 versions
Fixes #104
2015-07-24 12:36:26 -04:00
Dan Mulloy 7226e199e5 A few build improvements 2015-07-18 21:35:00 -04:00
Dan Mulloy 4c97c3c7ce Don't build TinyProtocol, it isn't actually a plugin 2015-07-18 21:09:02 -04:00
Dan Mulloy ae47ae3b89 Only suppress ClosedChannelExceptions 2015-07-18 21:07:41 -04:00
Dan Mulloy 900b31e0c9 Include Java versions in errors 2015-07-12 21:49:04 -04:00
Dan Mulloy f77ff6a0a2 Add /protocol dump, should help with debugging 2015-07-12 21:07:09 -04:00
Dan Mulloy 714e631dd0 Attempt to alleviate some issues with backwards compatibility
Addresses #98
2015-07-12 13:58:31 -04:00
Dan Mulloy b50133d322 Bump version to 3.6.5-SNAPSHOT 2015-07-12 12:17:48 -04:00
Dan Mulloy 0dd37bb801 Bump version to 3.6.4 for release 2015-07-11 16:52:27 -04:00
Dan Mulloy 5e703d91db Write Spigot's chat components as null in compiled modifiers too
Fixes #95
2015-07-07 13:55:31 -04:00
Dan Mulloy c7dab69cba Specifically write Spigot's chat components as null 2015-07-06 21:08:14 -04:00
Dan Mulloy 7861a57d37 Fix empty chat messages
Also improved incompatible plugin checking
2015-07-06 02:20:32 -04:00
Dan Mulloy 7b9e0cdb65 Fix timeout error with MCPC 1.6.4
Fixes aadnk#90
2015-06-28 15:55:11 -04:00
Dan Mulloy 74b1d93209 Add PlayerInfoData test 2015-06-26 22:50:31 -04:00
Dan Mulloy f825acb4d6 Revert changes to ItemStack handling, fix StreamSerializer 2015-06-26 22:50:02 -04:00
Dan Mulloy 47632e1fb1 Ignore log files generated by Log4J, fix an error with MCPC 2015-06-25 17:34:34 -04:00
Dan Mulloy b32d0d5fcd Attempt to restore compatibility with MCPC/Cauldron
Addresses aadnk#90, aadnk#83, and aadnk#71
2015-06-25 16:34:09 -04:00
Dan Mulloy 92fabb31b2 Minor restructure - move examples to modules, rename ShadedNetty 2015-06-24 11:48:07 -04:00
Dan Mulloy d012296ee0 Properly link to the Travis build status 2015-06-23 14:01:50 -04:00
Dan Mulloy fbc596d57b Add a generic enum converter and a set converter 2015-06-23 12:59:13 -04:00
Dan Mulloy 1b4a8ec0aa Customize the JavaDocs a bit 2015-06-21 23:36:07 -04:00
Dan Mulloy 63babc4ca1 Allow particles to be looked up using their name 2015-06-21 16:31:55 -04:00
Dan Mulloy 3f5abe8719 Fix a typo in WrappedSignedProperty 2015-06-20 16:19:52 -04:00
Dan Mulloy 84b502cba7 Fix constructor visibility 2015-06-19 22:18:46 -04:00
Dan Mulloy 9fe5df3cfe Properly obtain read/write methods for tile entities 2015-06-19 22:17:34 -04:00
Dan Mulloy e0f7c72e18 Use correct Guava package, fix a few compatibility issues 2015-06-19 13:14:20 -04:00
Dan Mulloy b1b5408c53 Don't fail the build if there are JavaDoc errors 2015-06-18 22:56:12 -04:00
Dan Mulloy 62ebcbb66d Properly aggregate the JavaDocs 2015-06-18 22:48:00 -04:00
Dan Mulloy cb0fdcff9d Set parent version to dev-SNAPSHOT 2015-06-18 22:07:08 -04:00
Dan Mulloy ee3db1e25a Fix netty compat module build failing 2015-06-18 22:04:29 -04:00
Dan Mulloy 165b4d3126 Only test the main plugin, fix JavaDoc errors 2015-06-18 21:54:18 -04:00
Dan Mulloy 25bdcd6bad Add the parent pom 2015-06-18 21:10:04 -04:00
Dan Mulloy 49fb3339be Git decided to ignore a good chunk of the files 2015-06-18 21:09:21 -04:00
Dan Mulloy 325b91fb1f Provide backwards compatibility with 1.7.10 and earlier
Also update the version to 3.6.4 since this was such a massive change
2015-06-18 21:07:32 -04:00
Dan Mulloy 9ce277cd76 Add Java 6 to Travis 2015-06-17 15:33:36 -04:00
Dan Mulloy 6af2d37319 And a few more 2015-06-17 15:23:13 -04:00
Dan Mulloy 273b470140 And a few more 2015-06-17 15:08:58 -04:00
Dan Mulloy bba1054f97 Fix a ton of JavaDoc errors 2015-06-17 14:25:39 -04:00
Dan Mulloy 241003b56a Clean up the pom a bit 2015-06-17 12:42:36 -04:00
Dan Mulloy e6b5d53234 Restore compatibility with Java 6 2015-06-17 12:19:29 -04:00
Dan Mulloy c3dacd06aa Link to Travis 2015-06-16 20:12:43 -04:00
Dan Mulloy 6e553bc59d Properly initialize required internals 2015-06-16 20:09:46 -04:00
Dan Mulloy f5f3eef988 Fix travis builds 2015-06-16 19:50:27 -04:00
Dan Mulloy 4e7bff40c0 Properly encode and decode x and z coordinates 2015-06-16 19:48:30 -04:00
Dan Mulloy 06eac8f67b Add .travis.yml 2015-06-15 17:10:38 -04:00
Dan Mulloy 2c5b3a2a5b Fix odd ArrayStoreException with MultiBlockChangeInfo
Also adapt test to test with arrays
2015-06-15 13:54:34 -04:00
Dan Mulloy 24d5b1739a Fix the MultiBlockChangeInfo wrapper
Also add a few tests to make sure it keeps working
2015-06-15 00:58:16 -04:00
Dan Mulloy 0d18bd2734 Attempt to fix a NPE with packet sending
Addresses #88
2015-06-09 14:19:37 -04:00
Dan Mulloy 423cc320f6 Add ability to convert to and from the BungeeCord Chat API
Fixes #87
2015-06-09 11:08:54 -04:00
Dan Mulloy 594b7ad075 Update to 1.8.7
Yay, no code changes!
2015-06-05 14:07:32 -04:00
Dan Mulloy 270cdfca2f Handle package seal 2015-06-03 16:28:09 -04:00
Dan Mulloy 20247747b5 Fix AttributeSnapshot, rewrite ItemStack handling, add more tests
Fixes #85
2015-06-02 14:57:30 -04:00
Dan Mulloy a718cc9067 Add a test to make sure all packets are registered 2015-05-31 14:42:52 -04:00
Dan Mulloy 9f2141331a Update subprojects to 1.8.6 2015-05-25 11:53:08 -04:00
Dan Mulloy 80fbd3ebc2 Update to 1.8.6 2015-05-25 11:36:05 -04:00
Dan Mulloy e0d4110cd2 Remove debug stacktrace 2015-05-22 14:38:27 -04:00
Dan Mulloy 99dea686a9 Update tests for 1.8.5 2015-05-22 14:35:58 -04:00
Dan Mulloy eca168edcf ProtocolLib works with 1.8.5
Yay no code changes necessary!
2015-05-22 13:47:14 -04:00
Dan Mulloy f89b71c5b4 Use my maven repo, builds exponentially faster now 2015-05-20 19:28:56 -04:00
Dan Mulloy abfb1ce9ac Apparently wrong JavaDocs kill the build now 2015-05-20 18:04:33 -04:00
Dan Mulloy 27f3f76c5e Deploy to my fancy new maven repo 2015-05-20 17:50:07 -04:00
Dan Mulloy 36313eabb1 Add wrapper for MultiBlockChangeInfo 2015-05-10 14:04:08 -04:00
Dan Mulloy 1da65317c4 Add in basic metadata API
Fixes #80
2015-04-26 15:47:44 -04:00
Dan Mulloy e4d1f8d992 Fix typo in Readme.md 2015-04-19 20:18:43 -04:00
Dan Mulloy 166c615725 Fix getProtocolVersion()
Fixes #74
2015-04-16 21:13:58 -04:00
Dan Mulloy 0cc182f93d Reimplement plugin incompatibility check, tweak some commands 2015-04-11 16:47:07 -04:00
Dan Mulloy 0ed76d7228 Use our scheduler with metrics 2015-04-09 20:42:54 -04:00
Dan Mulloy ce3069d490 Update metrics to revision 7, don't include build number with metrics 2015-04-09 20:36:57 -04:00
Dan Mulloy e31599073f Reimplement the last possible tests, add a few more 2015-04-06 18:19:00 -04:00
Dan Mulloy a2c9240011 Reimplement a bunch more tests 2015-04-06 17:30:01 -04:00
Dan Mulloy 0958c042c9 Fix some more failing tests 2015-04-05 19:50:47 -04:00
Dan Mulloy 1c6641748b Don't run tests with the legacy profile 2015-04-05 19:40:02 -04:00
Dan Mulloy 889d1f6bc4 Reimplement some tests, fix reloading
Fixes #68
2015-04-05 19:23:06 -04:00
Dan Mulloy c981ba8d62 Fix a few copyright headers, update packages in Constants 2015-04-03 17:24:28 -04:00
Dan Mulloy 5d80173f44 Fix block data, don't shade netty 2015-04-03 14:00:56 -04:00
Dan Mulloy a5689a5550 Handle uncaught exceptions to avoid console spam 2015-04-02 14:28:29 -04:00
Dan Mulloy 16111449b4 Restore compatibility with 1.8 2015-03-31 19:31:11 -04:00
Dan Mulloy d96ed73e82 Add build number to the version 2015-03-31 15:45:19 -04:00
Dan Mulloy 992be51646 Rewrite BukkitCloner to be more dynamic, fix some NPEs
Fixes #66
2015-03-30 23:20:13 -04:00
Dan Mulloy 0abe72d72b Don't bother printing warnings for nonexistant CustomTypes 2015-03-30 10:03:28 -04:00
Dan Mulloy 24e543b33b The login start packet is a better source for GameProfiles 2015-03-29 23:02:59 -04:00
Dan Mulloy d44741dd17 Update the .gitignore 2015-03-29 22:58:50 -04:00
Dan Mulloy addfacb19c Add backwards compatibility for versions 1.0 thru 1.7.10 2015-03-29 22:56:11 -04:00
Dan Mulloy 2c743e1c89 Fix an error involving async packets and logged off players
Fixes #65
2015-03-22 21:23:59 -04:00
Dan Mulloy ae8ba16a7e PlayerDigType is the second enum in the packet
Fixes #62
2015-03-22 15:38:14 -04:00
Dan Mulloy 16c85ac8b4 Chunk position no longer exists, don't sweat if we can't find it
Fixes #64
2015-03-22 15:30:45 -04:00
Dan Mulloy 37123c0aa1 Make async packets less scary 2015-03-22 15:26:01 -04:00
Dan Mulloy 923f4f4c4a Merge pull request #61 from yawkat/8-patch-0
Implement AnnotatedElement contract in MethodInfo
2015-03-21 12:03:35 -04:00
Jonas Konrad 0b4ce155ac Implement AnnotatedElement contract in MethodInfo 2015-03-21 14:32:42 +01:00
Dan Mulloy cb5633aba8 Merge pull request #60 from Techcable/patch-1
Only compile version pattern once
2015-03-16 16:03:33 -04:00
Techcable a078ddb6e6 Only compile pattern once 2015-03-15 23:01:24 -07:00
Dan Mulloy c7b9600c21 Fix a ClassNotFoundException with PlayerInfoData
Fixes #59
2015-03-15 13:10:36 -04:00
Dan Mulloy ac14843d96 Update to 1.8.3 2015-03-14 14:51:40 -04:00
Dan Mulloy 9a0c421b52 Update maximum supported version 2015-03-13 16:52:01 -04:00
Dan Mulloy f72a6d8695 Fix a ClassNotFoundException with AttributeSnapshot
Fixes #56
2015-03-09 16:43:25 -04:00
Dan Mulloy 7291837b9d Fix a few errors with inner classes 2015-03-07 18:27:48 -05:00
Dan Mulloy b0662b05c5 Initial update to 1.8.3 2015-03-07 17:57:47 -05:00
Dan Mulloy 0d4071f11e Use my maven repo, should fix compilation errors 2015-02-27 12:37:49 -05:00
Dan Mulloy 115b5e8daa [TinyProtocol] Format TinyProtocol 2015-02-17 16:53:09 -05:00
Dan Mulloy 66be4900a3 Warn if exceptions are caused by broken plugins 2015-02-14 21:57:45 -05:00
Dan Mulloy a3784b3cf1 Improve Util.getOnlinePlayers() 2015-02-12 17:02:38 -05:00
Dan Mulloy ac54cef2fd Add methods to convert NativeGameMode to and from GameMode
Fixes #45
2015-02-05 16:20:02 -05:00
Dan Mulloy 391970b185 Fix nonexistent field messages 2015-01-31 00:54:55 -05:00
Dan Mulloy bfb0683b1f Don't take fieldIndex into account when checking for nonexistent fields 2015-01-28 21:30:17 -05:00
Dan Mulloy 704fe60bd1 Provide more descriptive error messages for nonexistent fields 2015-01-28 21:28:28 -05:00
Dan Mulloy 1d711315e4 Add wrapper for BlockData
Closes #34
2015-01-26 17:26:07 -05:00
Dan Mulloy 8846b28469 Add support for Particle modification 2015-01-25 12:42:30 -05:00
Dan Mulloy 3dda5c6a0a Remove unused code, revert some unnecessary changes 2015-01-17 19:23:51 -05:00
Dan Mulloy 5e5eff5e5f Merge pull request #32 from DarkSeraphim/patch-1
Fix NPE when writing PlayerInfoData to packet
2015-01-08 20:10:31 -05:00
Mark Hendriks dd5bfd5f16 Fix NPE when writing PlayerInfoData to packet
By default, the listName field in EntityPlayer is null. As the WrappedChatComponent ignores null values (which in itself is completely fine), the field will be null when you read one while the EntityPlayer has no listName. When you try to write one, however, the converter directly accesses the handle from the WrappedChatComponent. This means you will be trying to access the handle of null when you don't explicitly set the displayName in such a packet.
2015-01-09 01:45:17 +01:00
Dan Mulloy 266e9e47cc Move null assignments after the proper checks 2015-01-04 17:54:12 -05:00
Dan Mulloy 489c72418d Add initial support for wire format packets 2015-01-03 23:18:43 -05:00
Dan Mulloy 6ddbbfb428 Use correct methods for WrappedChatComponent 2015-01-02 17:07:15 -05:00
Dan Mulloy 09e2312d80 More practical fix for #28 2015-01-01 21:44:38 -05:00
Dan Mulloy a1b989cb3a Revert changes to channel injection 2015-01-01 21:21:42 -05:00
Dan Mulloy a748a95197 Fix a few errors with injection 2015-01-01 13:53:46 -05:00
Dan Mulloy 79da4f92a1 Don't attempt to process packets after disconnect
Addresses #28
2015-01-01 12:48:05 -05:00
Dan Mulloy 5605c3817e Fix issues with determining async packets
Fixes #27
2014-12-31 18:26:35 -05:00
Dan Mulloy 10f9c4c9f9 Partial fix for async packets 2014-12-28 13:34:40 -05:00
Dan Mulloy 7d5e1b9c34 Update StreamSerializer to 1.8 2014-12-28 13:09:29 -05:00
Dan Mulloy 687db088ec Update versions in WrappedServerPing 2014-12-23 14:09:21 -05:00
Dan Mulloy 6f3329cae6 Fix some tests 2014-12-23 00:25:16 -05:00
Dan Mulloy 2ed358405d Actually fix PlayerInfoData constructor 2014-12-20 22:18:05 -05:00
Dan Mulloy 0af57b853e Organize PlayerInfoData by constructor order 2014-12-20 19:22:42 -05:00
Flo // Sheigutn 776eeb898f Fix PlayerInfoData constructor 2014-12-20 19:17:07 -05:00
Dan Mulloy 5f4867eb1f Add wrapper for PlayerInfoData 2014-12-18 21:25:49 -05:00
Dan Mulloy f145d32016 TagAPI no longer works, update minimum version 2014-12-13 23:44:01 -05:00
Dan Mulloy 821c3a1d48 Update maven plugin versions 2014-12-13 17:02:54 -05:00
Dan Mulloy 2a51e16ef0 Update Readme to contain Maven info 2014-12-09 17:41:37 -05:00
Dan Mulloy 4c1fac9aa3 [ItemDisguise] Update ItemDisguise to 1.8 2014-12-08 16:36:20 -05:00
Dan Mulloy a60e2522c9 [TinyProtocol] Update to 1.8 2014-12-08 16:24:27 -05:00
Dan Mulloy 74297da901 Add Spigot requirement in the Readme 2014-12-06 09:40:30 -05:00
Dan Mulloy 0bc1b7ecaf Correct casing of Vec3D 2014-12-05 21:45:45 -05:00
Dan Mulloy f6e0bcf6f5 Add support for getting vectors 2014-12-05 21:41:55 -05:00
Dan Mulloy 2c017c6dac Merge branch 'master' of https://github.com/dmulloy2/ProtocolLib.git 2014-12-05 18:41:33 -05:00
Dan Mulloy 58302f2477 Use local repository instead 2014-12-05 18:41:07 -05:00
Dan Mulloy 37d4fadbf1 Merge pull request #17 from Zeruska/master
Fix typo in EnumWrappers (PlayerInfoAction)
2014-12-05 09:03:06 -05:00
Zeruska 701885e4eb Fix typo in EnumWrappers (PlayerInfoAction) 2014-12-04 11:40:22 +01:00
Dan Mulloy f14623897b Move libraries to the local machine 2014-12-03 16:38:03 -05:00
Dan Mulloy 9a9c5446cc Rename some enum wrappers to match their NMS counterparts 2014-12-03 16:24:53 -05:00
Dan Mulloy 1a368299b4 Merge pull request #15 from Zeruska/master
Add more EnumWrappers (PlayerDiggingAction, EntityAction, UpdateScoreAct...
2014-12-03 16:15:07 -05:00
Zeruska d6b94e658a Add more EnumWrappers (PlayerDiggingAction, EntityAction, UpdateScoreAction) 2014-12-03 21:56:23 +01:00
Dan Mulloy dabbe3fca9 Clarify the compatibility section 2014-12-02 16:20:03 -05:00
Dan Mulloy 5b9acb72fb Remove extra space 2014-12-02 16:12:03 -05:00
Dan Mulloy 30bff505c4 Fix some spacing 2014-12-02 16:11:13 -05:00
Dan Mulloy fa1d419b66 Merge pull request #13 from Sheigutn/master
Add PlayerInfoAction EnumWrapper
2014-12-02 16:08:51 -05:00
Flo // Sheigutn ea734965bc Add PlayerInfoAction EnumWrapper 2014-12-02 19:29:10 +01:00
Dan Mulloy 954a37a261 Fix a few javadoc issues 2014-12-01 16:31:11 -05:00
Dan Mulloy 98eb6958e0 Add more EnumWrappers
Thanks @Zeruska!
2014-12-01 16:25:27 -05:00
Dan Mulloy 18a45aace0 Fix an issue getting chat component arrays 2014-12-01 16:11:27 -05:00
Dan Mulloy 1c8e3b5ee3 Add support for ResourcePackStatus 2014-11-30 18:44:10 -05:00
Dan Mulloy d7087c2da5 Make write error messages more informational 2014-11-30 18:29:22 -05:00
Dan Mulloy 7b08bb9e1b Fix some javadoc typos 2014-11-30 12:24:47 -05:00
Dan Mulloy 6abd0cfdaa Add support for accessing ChatComponent arrays
Fixes #7
2014-11-30 12:23:59 -05:00
Dan Mulloy 3ac9793553 Update EnumWrappers with new constants 2014-11-29 21:24:27 -05:00
Dan Mulloy dfc1808aca Update protocol version 2014-11-29 21:13:49 -05:00
Dan Mulloy 30008ddd2c Merge branch 'master' of https://github.com/dmulloy2/ProtocolLib.git 2014-11-29 17:12:34 -05:00
Dan Mulloy 2074c2cab1 Add getters for block position
Fixes #6
2014-11-29 17:12:24 -05:00
Dan Mulloy 9cab57fa1c Merge pull request #5 from Minecrell/enum_wrappers
Fix EnumWrappers referencing a changed packet
Thanks @Minecrell!
2014-11-29 15:30:14 -05:00
Minecrell c2fdd15677 Fix EnumWrappers referencing a changed packet 2014-11-29 21:14:20 +01:00
Dan Mulloy a501e50237 Increment version to 3.6.3-SNAPSHOT 2014-11-29 13:51:23 -05:00
Dan Mulloy 38cf8f4888 Add wrapped block position, fix some errors 2014-11-29 13:35:34 -05:00
Dan Mulloy 1f6b873f84 ChunkPosition was replaced by BlockPosition 2014-11-29 13:12:35 -05:00
Dan Mulloy d5a63e8e38 Update scm information in the pom.xml 2014-11-29 12:17:08 -05:00
Dan Mulloy 670e8f034c Fix players being disconnected on login 2014-11-29 11:58:43 -05:00
Dan Mulloy b9818cace7 Update some version stuff 2014-11-28 22:36:47 -05:00
Dan Mulloy bd8f2dcc41 Skip tests for now 2014-11-28 22:24:00 -05:00
Dan Mulloy 9b88847c28 Actually update to 1.8 2014-11-28 22:08:46 -05:00
Dan Mulloy 075ef28b5e Add libraries, update support URL 2014-11-28 11:29:49 -05:00
Dan Mulloy 077ea6ea08 Initial update to 1.8 - "It compiles" 2014-11-28 11:22:10 -05:00
Dan Mulloy d7e46215b8 Format pom.xml and plugin.yml, completely remove updater 2014-11-27 20:35:30 -05:00
Dan Mulloy 7e168e2655 Update JavaDoc link in the readme 2014-11-24 11:48:04 -05:00
Dan Mulloy e69832a82a Remove the updater for now 2014-11-24 11:41:04 -05:00
Dan Mulloy e1e0c4a05a Don't shade spigot 2014-11-15 18:19:36 -05:00
Dan Mulloy 7a3b9c9e2b Add download link to Readme 2014-11-15 18:07:09 -05:00
Dan Mulloy 6044822014 Make it compile
We'll worry about the tests later
2014-11-15 17:58:27 -05:00
Dan Mulloy d76788b092 Update package names in BukkitInitialization 2014-11-15 17:01:31 -05:00
Dan Mulloy 03260969ff Name the built jar ProtocolLib.jar 2014-11-15 16:52:03 -05:00
Dan Mulloy 5804f0520f Merge Spigot protocol hack support into main branch 2014-11-15 16:44:58 -05:00
Dan Mulloy ca2bc3ecc5 Attempt to fix memory leaks with the ChannelInjector
Addresses https://github.com/aadnk/ProtocolLib/issues/70
2014-11-15 14:56:57 -05:00
Dan Mulloy 592874fd5e Delete a bunch of Eclipse files 2014-11-15 14:46:59 -05:00
Dan Mulloy bd74d0df1d Update individual projects' .gitignores 2014-11-15 14:29:09 -05:00
Dan Mulloy 4043f67610 Remove unused variable 2014-11-15 14:26:00 -05:00
Dan Mulloy eebcdf3ea1 Update TinyProtocol 2014-11-15 13:10:42 -05:00
Dan Mulloy 726b7eb8ab Delete org.eclipse.m2e.core.prefs 2014-11-15 13:07:46 -05:00
Dan Mulloy c61de2766d Delete org.eclipse.jdt.ui.prefs 2014-11-15 13:07:42 -05:00
Dan Mulloy eb27b03721 Delete org.eclipse.jdt.core.prefs 2014-11-15 13:07:35 -05:00
Dan Mulloy ffa85b80ee Delete org.eclipse.core.resources.prefs 2014-11-15 13:07:26 -05:00
Dan Mulloy ac05781111 Update .gitignore 2014-11-15 13:06:34 -05:00
Dan Mulloy cfb5944990 Update to 1.7.10, update readme, organize imports 2014-11-15 13:02:03 -05:00
Kristian S. Stangeland d8181b7bf3 Use the correct sender instance. 2014-09-24 01:00:02 +02:00
Kristian S. Stangeland 25cb648cfa Prevent plugins from sending a packet in the wrong direcetion. 2014-09-18 21:56:32 +02:00
Kristian S. Stangeland 78b6a651dc Add a way to skip the plugin verifier. 2014-09-07 21:28:01 +02:00
Kristian S. Stangeland f0fd904396 Adding support for retrieving the protocol version. 2014-09-05 02:00:39 +02:00
Kristian S. Stangeland b901f029d2 [TinyProtocol] Adding to Maven repository under version 0.1.0 2014-08-22 14:32:03 +02:00
Kristian S. Stangeland b68579bcb7 Switch to yellow instead of blue, as its more readable in console. 2014-08-05 03:52:37 +02:00
Kristian S. Stangeland c443fc3da6 Execute onPacketSending() on the main thread for monitor listeners.
A special-case occurs when a plugin sends a packet to a client 
with filters set to FALSE (that is, bypassing most packet listeners) - 
a new packet event is constructed solely for all MONITOR listeners, as
they are informed regardless of the value of FILTER.

Unfortunately, the sending method may be invoked on a thread other 
than the main thread, which will invoke onPacketSending() 
asynchronously. This violate the assumed thread affinity of 
onPacketSending(), so we will now schedule the packet sending on 
the main thread to correct this - but only if there are monitor
listeners, and they have not specified ListenerOptions.ASYNC (which 
means onPacketSending() is thread safe).
2014-08-02 23:39:29 +02:00
Kristian S. Stangeland b3cda21fe5 Correct the WrappedGameProfile unit test. 2014-07-30 03:49:37 +02:00
Kristian S. Stangeland 4bd9a1f01e Warn plugins that never call start/syncStart on AsyncListenerHandler.
This is almost always incorrect as it will simply block the 
transmission or reception of every packet of a given type.
2014-07-28 01:16:23 +02:00
Kristian S. Stangeland ba88d292c0 Last commit added to the API. Incrementing minor version to 3.5.0 2014-07-26 17:29:35 +02:00
Kristian S. Stangeland f378b7bbd9 Added support for closing asynchronous listeners by packet listener. 2014-07-26 17:28:05 +02:00
Kristian S. Stangeland d717ff1586 Hack for WrappedSignedProperty on Cauldron 1.7.10. FIXES #54 2014-07-23 03:28:12 +02:00
Kristian S. Stangeland 4ccd8853c4 Allow any string in WrappedGameProfile(String, String).
We now accept any string in this constructor, to preserve 
backwards compatibility. But, we depreciate its use, as 
WrappedGameProfile(UUID, String) can be used in every Minecraft
version that supports a game profile.

There's also a new warning system that will identify the plugin 
that is using the depreciated method, and print its name to the 
console (at most once every hour).
2014-07-14 04:54:44 +02:00
Kristian S. Stangeland 09e45977f2 Added support for Guava 11 (and later) in Spigot #1506-1508.
Note that build #1509 reverted to Guava 10, so this is merely a 
future proof.
2014-06-27 11:57:34 +02:00
Kristian S. Stangeland 7e5abecfe2 Increment to 3.4.1-SNAPSHOT 2014-06-27 11:37:05 +02:00
Kristian S. Stangeland aaecb08892 Added MassiveCore to the list of plugins exluded from the verifier. 2014-06-23 02:50:47 +02:00
Kristian S. Stangeland 0375ad5391 [TinyProtocol] Fix NULL player in the packet interception methods. 2014-06-19 15:28:27 +02:00
Kristian S. Stangeland 0c940711ac Increment version to 3.4.0. 2014-06-16 00:29:59 +02:00
Kristian S. Stangeland c50bc55f77 Improve JavaDoc documentation of WrappedGameProfile. 2014-06-12 21:16:31 +02:00
Kristian S. Stangeland ce216d0371 Add a way to retrieve the underlying UUID in WrappedGameProfile. 2014-06-11 21:05:08 +02:00
Kristian S. Stangeland 5b21f4d61c Don't cache equivalent converters in StructureModifier.
This should hopefully fix a memory leak discovered by Stevenpcc 
(thanks).
2014-06-10 23:01:24 +02:00
Kristian S. Stangeland 1141c0ba71 Ignore NuSuchElementExceptions when removing channel handlers. 2014-05-21 23:04:29 +02:00
Kristian S. Stangeland 8067799306 Merge pull request #48 from glen3b/patch-1
Minor improvements to ClassLoader
2014-05-18 12:30:52 +02:00
Glen Husman 1ee68d4e09 attemptLoadFrom will throw IllegalArgumentException for arrays of length
1 with a null element
2014-05-17 20:00:40 -07:00
Kristian S. Stangeland 82be6bfecc May solve a race condition in ticket 220 on BukkitDev.
It is possible, though not confirmed, that ProtocolLib has not been
fully cleaned up after a "reload" command and the next instance of 
ProtocolLib is loaded. In that case, it may be possible that a channel
is injected in the main thread while its cleanup procedure is still
running.

This is an attempt to solve this problem. Though, it is not confirmed
to work.
2014-05-17 23:01:27 +02:00
Glen Husman 0f5be7f1f1 Add ClassSource.empty, ClassSource.attemptLoadFrom will also not
tolerate nulls
2014-05-17 12:23:59 -07:00
Kristian S. Stangeland b272322105 Fix a bug preventing ClassSource.retry() from working correctly.
We returned the class of the other ClassSource, instead of invoking
its loadClass() method.
2014-05-17 17:29:37 +02:00
Kristian S. Stangeland bbaea894d4 Merge pull request #49 from glen3b/patch-2
Update sample code in readme
2014-05-17 17:13:38 +02:00
Kristian S. Stangeland d3e37df343 Added the ability to toggle whether or not a given player is injected.
We also ensure we can run multiple instances of TinyProtocol without
requiring implementers to override getHandlerName(). 

Also fixed a potential memory leak, as the channel map was set to 
weakKeys() instead of the correct weakValues().
2014-05-17 17:10:17 +02:00
glen3b b2425dcc6b Update sample code in readme
The current sample code uses the magic packet IDs, which are deprecated in the current version of the library. This commit changes the sample code to use the non-deprecated enum-like packet identifiers, the sample code which is present on the BukkitDev page for this plugin (with some minor tweaks).
2014-05-14 21:30:40 -07:00
Glen Husman 95087a5b9f attemptLoadFrom now ignores null values properly 2014-05-14 16:51:56 -07:00
Glen Husman e0449b2db6 Add ClassSource.attemptLoadFrom method
This method is an alternative to chaining retry calls
2014-05-14 16:40:22 -07:00
Glen Husman 324e6de284 Document new fromMap behavior 2014-05-14 16:26:51 -07:00
glen3b ccecdf216f Throw ClassNotFoundException when appropriate
Currently, the ClassSource returned by ClassSource.fromMap will return null if the Class cannot be found (as that is the behavior of maps). However, other ClassSources throw a ClassNotFoundException if the class cannot be loaded. This commit changes the behavior of ClassSource.fromMap to throw a ClassNotFoundException if the class was not found in the map (or was mapped to null). This commit also changes the method to interpret a null map as an empty map.
2014-05-14 16:06:26 -07:00
Kristian S. Stangeland 818ac5cbde Correct a bug that would lead to IllegalStateException in plugins.
This was caused by the fact that "requireInputBuffer" used 
findLegacy(int) to get the correct PacketType, instead of 
findLegacy(int, Sender.CLIENT). The latter is justified by the fact 
that only client-side packets require an input buffer.
2014-05-11 15:33:15 +02:00
Kristian S. Stangeland d415bd7643 Workaround for ServerConnection not being constructed yet. 2014-05-11 14:26:12 +02:00
Kristian S. Stangeland 180caed6b9 Allow users to construct signed property instances. 2014-05-10 03:47:45 +02:00
Kristian S. Stangeland a6dfa60d79 Merge pull request #47 from glen3b/patch-1
Update MinecraftReflection exception messages
2014-05-03 19:25:35 +02:00
Kristian S. Stangeland 8a2e696363 Adding partial support for MCPC-Plus 1.7.2.
This doesn't include handling the different package names of 
net.minecraft.util.io.netty in MCPC.
2014-05-03 19:13:20 +02:00
glen3b 0c9f5e8992 Update MinecraftReflection exception messages
The messages incorrectly identified the minecraft server package as "net.mineraft.server"
This commit fixes that
Although it is only a minor spelling error, it is worth fixing
2014-05-02 19:44:33 -07:00
Kristian S. Stangeland 71ce362c8e Add support for reading and writing tile entities. 2014-05-03 02:52:03 +02:00
Kristian S. Stangeland f9cd2b3db4 Fix JavaDoc of a copy-paste error. 2014-05-02 17:17:01 +02:00
Kristian S. Stangeland 2244f986bb Print a hex dump in the case of very large arrays. 2014-05-02 03:50:40 +02:00
Kristian S. Stangeland d8e8a88076 Add the "compare" flag to the "packet add" command.
This instructs ProtocolLib to fetch the original state of the packet, 
before its processed by any packet listeners above LOWEST. Then, 
it displays this state in the console, along with the final state as 
retrieved in MONITOR.
2014-05-01 20:14:03 +02:00
Kristian S. Stangeland 1ef602416d Added the ability to schedule packets after an event has succeeded.
Also fixes post listeners and asynchronous packet listeners.
2014-04-26 00:21:05 +02:00
Kristian S. Stangeland f7c4fd4ec9 Added support for "post packet events".
NetworkMarker now contains a list of post listeners that are invoked 
(in no particular order) when a packet has been serialize and sent
to a player, OR, when it has been enqueued for processing by the 
server.

This works for both 1.7.2+ (Netty) and 1.6.4 and earlier, though the
1.6.4 version has a good deal more overhead.
2014-04-25 02:55:17 +02:00
Kristian S. Stangeland 5185442f35 Added support for retrieving the signed property map of a profile.
In addition, we also allow WrappedGameProfile to be retrieved from a
player.
2014-04-21 15:51:38 +02:00
Kristian S. Stangeland ce8d115b5a Incrementing to 3.4.0-SNAPSHOT 2014-04-21 15:50:54 +02:00
Kristian S. Stangeland 58e66b80da Release of 3.3.1 2014-04-18 00:27:41 +02:00
Kristian S. Stangeland 17dda7f032 Better to fail updating the version number than crashing.
If we update the number, we have to also use the "empty file" trick
to remove the old file, which will crash ProtocolLib on the first 
reload. It takes a second reload for it to function at all.

It's much better to take the hit on the version number, and avoid 
this issue altogether. The update method simply wasn't designed for 
plugins with version numbers in their file name.
2014-04-18 00:27:25 +02:00
Kristian S. Stangeland 19ea5dfc33 Never downgrade ProtocolLib after an update.
Also ensure the version number is incremented after an update.
2014-04-18 00:08:18 +02:00
Kristian S. Stangeland 3bd1361c9a Add the ability to clonse java.util.Arrays$ArrayList. 2014-04-16 02:45:00 +02:00
Kristian S. Stangeland 486ac2653d Remove pointless line. 2014-04-16 02:18:43 +02:00
Kristian S. Stangeland c1890cb256 Interpret the empty string as 0-0-0-0-0 as well. 2014-04-14 17:49:04 +02:00
Kristian S. Stangeland a65eac6471 Use the newer server ping version (5) in 1.7.8 2014-04-14 14:18:58 +02:00
Kristian S. Stangeland 0a4b1b3201 Merge pull request #45 from Johni0702/master
Fix NPE in WrappedGameProfile.getId()
2014-04-14 14:16:09 +02:00
johni0702 2476ab67a6 Fix NPE in getId() 2014-04-14 09:53:05 +02:00
Kristian S. Stangeland 74947c02af Incrementing to 3.3.1-SNAPSHOT 2014-04-12 23:57:41 +02:00
Kristian S. Stangeland e0b0e51342 Updating to 1.7.8
This was necessitated by two new NMS changes:
* NBTCompressedStreamTools.a(DataInput, int) now includes an additional
  parameter NBTReadLimiter
* GameProfile changed the type of getId() from String to UUID, along
  with the constructor (String, String) to (UUID, String).
2014-04-12 23:56:11 +02:00
Kristian S. Stangeland c84a5d7fa2 Handle the possibility that getSuperclass() returns NULL.
Thanks to libraryaddict for discovering this bug.
2014-04-10 20:24:24 +02:00
Kristian S. Stangeland 4dea26b847 [TinyProtocol] Add support for intercepting login and status packets. 2014-04-10 01:37:56 +02:00
Kristian S. Stangeland be238f6ac6 Don't remove channel handlers in the main thread - can case deadlock. 2014-04-09 02:57:52 +02:00
Kristian S. Stangeland 8cb9e050a5 Don't attempt to register a client packet as a server packet.
And vice versa. This is a leftover from the time we used ConnectionSide
and packet IDs.
2014-04-08 02:00:13 +02:00
Kristian S. Stangeland a800a505f5 Update gitignore 2014-04-08 01:59:37 +02:00
Kristian S. Stangeland 24de835440 Add the ability to load or save NBT compounds from files. 2014-04-01 23:20:24 +02:00
Kristian S. Stangeland 4f871c64d7 Warn instead of throwing an exception on packet type change.
We don't want to crash plugins over this, since it doesn't 
automatically cause problems. But it may trip up plugins that 
assume the packet types they set when registering a listener is the 
only ones they'll ever recieve in the method body, which is not true
if a preceeding packet listener can change a packet to an arbitrary 
type.

I'm open for better suggestions here. But for now, I'll just print a 
warning and hope people use sendServerPacket() instead.
2014-03-30 00:50:45 +01:00
Kristian S. Stangeland 839c186609 Added a way to compute a "histogram" (ordered by time slices).
This allows us to see snapshots of the online statistics algorihm.
2014-03-29 23:29:25 +01:00
Kristian S. Stangeland 300d3c2475 Handle errors in Apache. 2014-03-28 20:50:10 +01:00
Kristian S. Stangeland 485c3856c1 Don't fetch NetworkMarker uncessarily. 2014-03-27 23:23:45 +01:00
Kristian S. Stangeland 3b26940385 Don't poll the current time more than once a second for updates. 2014-03-27 21:00:36 +01:00
Kristian S. Stangeland 3257d6069a Compare packet type with equals(), just in case. 2014-03-22 23:21:57 +01:00
Kristian S. Stangeland 45e236b54a Adds some sanity checks in PacketEvent.setPacket().
Thanks libraryaddict. :)
2014-03-22 23:20:16 +01:00
Kristian S. Stangeland 7adb6548aa Ensure output handlers function correctly in 1.7.2 2014-03-21 22:19:24 +01:00
Kristian S. Stangeland bd3a60b7eb Don't intercept catastrophic errors such as ThreadDeath and OOM.
May fix ticket 206.
2014-03-20 23:26:07 +01:00
Kristian S. Stangeland d76d4247b0 Ensure Player.getAddress() returns the client's address.
I mistakenly returned the server's address instead.
2014-03-16 03:51:44 +01:00
Kristian S. Stangeland 5e0000d139 Properly cancel or modify packets intercepted in the channel thread.
This fixes ticket #204 on BukkitDev.
2014-03-15 19:25:40 +01:00
Kristian S. Stangeland ad060b10af Fixed a bug introduced in #218 causing packet loops. FIXES 202.
In 659f01cc63, I attempted to 
execute packet listeners for receiveClientPacket() on the channel
thread, inadvertently causing them to be executed regardless if 
filtered was FALSE, and twice if it is TRUE.

Since asynchronous packet listeners use this feature to take out
packets from the packet stream, they wound up causing an infinite 
packet loop. This prevented them from ever being received by the
server.
2014-03-15 03:59:28 +01:00
Kristian S. Stangeland 75a5efcaa1 Add the ability to see registered asynchronous listeners. 2014-03-13 23:55:33 +01:00
Kristian S. Stangeland f4f8817fd5 Undo mocking when we're done with the test. 2014-03-13 03:34:51 +01:00
Kristian S. Stangeland 38c62c4cfd Ensure sendServerPacket() works in the channel thread as well.
If we're already in the channel event thread, we won't go through
the ChannelProxy code, so, check scheduledProcessing in the write 
method as well.
2014-03-13 03:02:41 +01:00
Kristian S. Stangeland 4259a86740 Optimize lookup of getBukkitEntity. 2014-03-03 23:11:05 +01:00
Kristian S. Stangeland b84aace585 Increment to 3.3.0-SNAPSHOT. 2014-02-23 22:49:46 +01:00
Kristian S. Stangeland e8c77af92b Get rid of the other weak maps, and use thread local fields instead.
This should improve performance, and prevent the possibility of not 
informing plugins of sent packets.
2014-02-23 22:13:46 +01:00
Kristian S. Stangeland 659f01cc63 Remove concurrent "processed" and "ignore" hash maps.
We can rely on the fact that channel handlers are processed in a single 
thread, and simply enable/disable a flag.
2014-02-23 18:55:41 +01:00
Kristian S. Stangeland 6e0a44f9df Don't cache the NetHandler when checking if it has changed. 2014-02-21 19:28:07 +01:00
Kristian S. Stangeland 5f66dc6b4a Update to 3.2.2-SNAPSHOT. 2014-02-21 19:15:31 +01:00
Kristian S. Stangeland eb7760236f Don't update the player instances on the LOGIN packet.
Instead, always check for the presence of NetServerHandler, 
and update then.
2014-02-21 19:14:58 +01:00
Kristian S. Stangeland eb421dd31e Don't load converters that belong in 1.7.2. 2014-02-21 19:14:05 +01:00
Kristian S. Stangeland 7503470cb1 No need to throw an exception if the "last updated" file is corrupt. 2014-02-18 21:46:42 +01:00
Kristian S. Stangeland 6443de6257 Properly display blocks using the packet monitoring system. 2014-02-15 12:47:30 +01:00
Kristian S. Stangeland a8378d4e6f Regression: Add back support for CraftBukkit 1.1.0 2014-02-11 23:58:57 +01:00
Kristian S. Stangeland 4e650ec7bf Quote replacement string. 2014-02-11 13:44:56 +01:00
Kristian S. Stangeland f71ed90b04 Refresh the Netty registry when packet listeners are added. FIXES #40 2014-02-07 19:37:08 +01:00
Kristian S. Stangeland 345cc74b3c Fixed a bug preventing dual side packets from working in 1.6.4.
An example of a dual side packet is Packet101CloseWindow, which is 
sent by the server when it forces a inventory window to close, or 
by the client to the server when the player voluntarily closes it.

The bug prevented the client-side of a dual side packet from being 
recognized in 1.6.4. Thanks to Shevchikden for discovering the bug, 
and finding the correct build number where it was introduced.
2014-02-04 22:53:56 +01:00
Kristian S. Stangeland 9ef9475b61 Correct a concurrency bug discovered by mibby. FIXES #39.
It's another lazy initialization problem. I only check a single 
field before initializing two related fields, which can cause 
problems when two threads execute handleLogin() concurrently. 

If thread A detects that PACKET_LOGIN_CLIENT is null, it updates both
it and LOGIN_GAME_PROFILE. However, thread B may only see the 
PACKET_LOGIN_CLIENT update, and still believe LOGIN_GAME_PROFILE is 
NULL. Hence why it causes a NullPointerException in issue #39.
2014-02-03 01:38:52 +01:00
Kristian S. Stangeland ea7b550bda Use PacketType in PacketContainerTest. 2014-02-02 19:54:49 +01:00
Kristian S. Stangeland 8bd177daf5 Update the integration test for Minecraft 1.7.2. 2014-02-02 19:46:03 +01:00
Kristian S. Stangeland 12286ebaa7 Expose configuration to plugins. 2014-02-02 19:45:30 +01:00
Kristian S. Stangeland b59f55e2e5 Added support for serializing/deserializing WrappedServerPing to JSON. 2014-02-02 16:50:56 +01:00
Kristian S. Stangeland 752807fa5f Split up TinyProtocol and its built-in reflection system. 2014-02-01 16:38:24 +01:00
Kristian S. Stangeland 96cc518fb0 Throw a more descriptive error of a packet type hasn't got a class. 2014-01-30 02:04:38 +01:00
Kristian S. Stangeland 6e3583b300 Increment to 3.2.1-SNAPSHOT
Use Maven resource filtering to automatically update plugin.yml.
2014-01-30 02:03:09 +01:00
Kristian S. Stangeland cfde9ecce8 Incrementing to 3.2.0 2014-01-29 04:00:40 +01:00
Kristian S. Stangeland ef4bd2ddcd Race condition that would occationally skip STATUS packets. Fixes 198 2014-01-29 01:06:05 +01:00
Kristian S. Stangeland bf8c16e22d Construct missing packet types. Possible fix for #197. 2014-01-28 01:38:34 +01:00
Kristian S. Stangeland fb07a91fb0 Updating TinyProtocol significantly.
Added the ability to send and recieve packets, along with a more 
powerful reflection system. You can now retireve Minecraft classes 
by name.
2014-01-28 00:58:57 +01:00
Kristian S. Stangeland 5d7895d741 Fixed a VERY long standing bug discovered by libraryaddict.
Constructing a WrappedWatchableObject with a 
net.minecraft.server.ItemStack would cause ProtocolLib to throw an
IllegalArgumentException, even though WrappedWatchableObject.
setValue(Object) accepts ItemStacks without trouble.

This is because WrappedWatchableObject.getUnwrappedType() didn't handle 
ItemStacks at all.
2014-01-23 02:55:47 +01:00
Kristian S. Stangeland 385bae8bda Increment to 3.1.3-SNAPSHOT 2014-01-20 06:21:51 +01:00
Kristian S. Stangeland 7ae0399721 Lookup packet types by name as well. 2014-01-20 06:21:20 +01:00
Kristian S. Stangeland 5cffccd8ca Bumping to version 3.1.2 2014-01-19 17:01:45 +01:00
Kristian S. Stangeland c87c0a9299 Add support for legacy /packet commands. 2014-01-19 00:27:57 +01:00
Kristian S. Stangeland d70e9d28d4 Properly handle WrappedGameProfile during packet cloning. 2014-01-17 23:58:04 +01:00
Kristian S. Stangeland 90548f1092 Periodically clean up injectors in Spigot 1.6.4 and earlier.
This (may) fix a fairly serious memory leak.
2014-01-17 02:51:22 +01:00
Kristian S. Stangeland eea0d131bb Updated the timings system to the new PacketType format.
Also made PacketType comparable.
2014-01-16 04:04:23 +01:00
Kristian S. Stangeland 0c718f9484 Attempting to correct a memory leak in ticket #189. 2014-01-13 03:46:06 +01:00
Kristian S. Stangeland e23129540c Ensure WrappedGameProfile can handle NULL profiles.
We also implement hashCode() ourselves, to avoid Mojang's buggy 
version that doesn't handle NULL.
2014-01-11 02:24:56 +01:00
Kristian S. Stangeland 4f95b515ae Complain about lack of page index to packet page. 2014-01-11 02:13:41 +01:00
Kristian S. Stangeland a902c1f186 Add support for using multiple lines in WrappedServerPing. 2014-01-09 20:01:37 +01:00
Kristian S. Stangeland 75fe2c6db9 Merge pull request #37 from Minecrell/patch-3
Correctly modify player sample with hidden player counts.
2014-01-09 08:30:03 -08:00
Minecrell 7ed24e72f6 Correctly handle player samples set to null. 2014-01-09 17:25:53 +01:00
Minecrell 2a729cfe2d Return an empty list instead of null when getting the player sample. 2014-01-09 17:18:34 +01:00
Minecrell 3e7fe52681 Correctly modify player sample with hidden player counts.
At the moment the setPlayers() method will throw a NullPointerException
if the player count has been hidden. This will correctly reset the
player counts before setting the player list and return null in the
getter instead of throwing an exception.
2014-01-09 16:45:05 +01:00
Kristian S. Stangeland 31b26fd0c5 Catch more serious errors in the plugin verifier.
It doesn't make sense for the caller to handle those.
2014-01-08 14:53:03 +01:00
Kristian S. Stangeland 7e9860f4c0 ProtocolLib is working fine in 1.7.4 2014-01-02 16:01:04 +01:00
Kristian S. Stangeland a7cac0be7f Forgot to include the wrapper itself. 2014-01-02 07:23:50 +01:00
Kristian S. Stangeland c06aeb2836 Add a quick and dirty wrapper for statistics maps. 2014-01-02 07:23:27 +01:00
Kristian S. Stangeland 5c92a46cfa Correctly clone WrappedServerPing when the player count is hidden. 2014-01-01 17:16:14 +01:00
Kristian S. Stangeland a0280a76d4 Merge pull request #36 from Minecrell/patch-1
Correctly handle wrapped server pings with no favicon.
2014-01-01 08:10:46 -08:00
Minecrell 14c69e294a Correctly handle wrapped server pings with no favicon.
In the current ProtocolLib release (3.1.0) the getFavicon() method will
throw a NullPointerException if the server is not sending a favicon.
This was partly fixed in 3c5482f but there's still no way of checking if
the server is sending a favicon, without checking if the encoded output
of the CompressedImage.toEncodedText() will return a valid result.
This commit will make the favicon getter and setter in the server ping
correctly handle pings with no favicon, by returning null for
getFavicon() (if no favicon will be displayed) and allowing to hide the
favicon by setting the favicon to null.
2014-01-01 16:29:21 +01:00
Kristian S. Stangeland a9f1ee48a9 Add the ability to hide the player count and maximum in ServerPing. 2014-01-01 04:21:31 +01:00
Kristian S. Stangeland 3c5482f79b Optimize CompressedImage slightly, and construct from base64 PNG. 2013-12-31 23:01:17 +01:00
Kristian S. Stangeland e0fed34fe4 Make it possible to look up the enum name of a PacketType. 2013-12-30 15:25:34 +01:00
Kristian S. Stangeland 58c027f162 Print caught exceptions in debug mode. 2013-12-29 11:58:54 +01:00
Kristian S. Stangeland 32758061a9 Increment to 3.1.1-SNAPSHOT 2013-12-28 14:53:48 +01:00
Kristian S. Stangeland ce064643ff Bumping to version 3.1.0 2013-12-27 05:26:48 +01:00
Kristian S. Stangeland e56c0fec00 A possible fix for a rare but game-breaking deadlock.
Calling remove() in the main thread will block the main thread, which 
may lead to a deadlock:
    http://pastebin.com/L3SBVKzp

ProtocolLib executes this close() method through a PlayerQuitEvent in 
the main thread, which has implicitly aquired a lock on 
SimplePluginManager (see SimplePluginManager.callEvent(Event)). 
Unfortunately, the remove() method will schedule the removal on one of 
the Netty worker threads if it's called from a different thread, 
blocking until the removal has been confirmed.
 
This is bad enough (Rule #1: Don't block the main thread), but the real 
trouble starts if the same worker thread happens to be handling a server
ping connection when this removal task is scheduled. In that case, it 
may attempt to invoke an asynchronous ServerPingEvent 
(see PacketStatusListener) using SimplePluginManager.callEvent(). But, 
since this has already been locked by the main thread, we end up with a 
deadlock. The main thread is waiting for the worker thread to process 
the task, and the worker thread is waiting for the main thread to 
finish executing PlayerQuitEvent.

TLDR: Concurrenty is hard.
2013-12-24 02:15:22 +01:00
Kristian S. Stangeland 16dd2d5d1b Add support for legacy mod packet IDs (such as 211). May fix issue #32 2013-12-22 20:10:51 +01:00
Kristian S. Stangeland db8d08f602 Add a mechanism for printing debug reports. 2013-12-22 17:31:31 +01:00
Kristian S. Stangeland d71bea9f8a Don't pass NULL to ConcurrentHashMap when reading or removing.
May fix issue 181 on BukkitDev.
2013-12-22 01:01:35 +01:00
Kristian S. Stangeland 5267e73311 Adding TinyProtocol to the example directory. 2013-12-21 19:37:02 +01:00
Kristian S. Stangeland d62955dc71 Moving ItemDisguise to an examples folder. 2013-12-21 19:17:06 +01:00
Kristian S. Stangeland af58dd8d03 Add the ability to look up PacketType from class. 2013-12-19 04:45:30 +01:00
Kristian S. Stangeland acea92ef5c Switch to using PacketType in the built-in commands.
The format is now as follows:
    /packet add server play 0-255.
    
It may be possible to look up packet types by name later.
2013-12-19 02:17:06 +01:00
Kristian S. Stangeland 6bd8bd2ca2 Handle plugin reloading properly, uninjecting all player channels.
This should prevent any crashes if the plugin is reloaded during 
a player login.
2013-12-17 16:45:01 +01:00
Kristian S. Stangeland 5805150d8c Call the updated player instance in the temporary player.
This allows us to provide additional information to our PacketEvent 
(mostly OfflinePlayer information), without sacrificing sendMessage() 
or getAddress(), which doesn't work in CraftPlayer during login.
2013-12-17 11:53:26 +01:00
Kristian S. Stangeland 9d972b90ac Update the player instance during PlayerLoginEvent as well.
This ensures that we at least have an OfflinePlayer when certain
packets are intercepted, instead of the TemporaryPlayer created 
by ProtocolLib.
2013-12-17 11:07:44 +01:00
Kristian S. Stangeland 936e0f0e82 Actually fix the ServerPing wrapper. 2013-12-15 22:54:59 +01:00
Kristian S. Stangeland aedd97dfef Ensure ProtocolLib functions in 1.6.4 and below. 2013-12-14 19:17:48 +01:00
Kristian S. Stangeland 96f24167bb Don't synchronize with the main thread when registering async listeners 2013-12-14 18:08:08 +01:00
Kristian S. Stangeland 88a2385b1e Set the correct option when calling optionAsync(). 2013-12-14 17:49:51 +01:00
Kristian S. Stangeland f41efdec60 Make it possible to modify the player list in the ping packet. 2013-12-14 17:35:29 +01:00
Kristian S. Stangeland a6f4aaa09a Fix a race condition we created while fixing another race condition.
They're everywhere.
2013-12-14 16:32:12 +01:00
Kristian S. Stangeland b52ea72903 Don't override the network manager list unnecessarily. 2013-12-14 05:02:43 +01:00
Kristian S. Stangeland 85d415de7c Correct a missing noEntryValue in Spigot.
Again.
2013-12-14 04:39:12 +01:00
Kristian S. Stangeland 9b349299a0 Significantly reduce the possibility of a race condition.
The vanilla server bootstrap (ServerConnectionChannel) is executed 
asynchronously when a new channel object has been registered in an event
loop, much before it is ready to accept a new client connection. It is 
here the whole channel handler pipeline is set up, along with a 
NetworkManager responsible for reading and writing packets. 

The trouble starts when the bootstrap class adds the created 
NetworkManager to a list (f) of managers in  ServerConnection. This list
is regularly inspected by the main thread (in order to process packets 
on the main thread) and includes a clean up procedure 
(ServerConnection#61) in case it detects a disconnected network manager.

Unfortunately, the network manager IS considered disconnected for a 
moment when its added to the list, so the main thread MAY end up 
getting to the network manager before Netty has connected the channel.
This is still very rare under normal circumstances, but because 
ProtocolLib does a lot of initial processing in the channel handler, the
asynchronous thread gets hold up for a long while the first time a 
player connects to the server, allowing the main thread sufficient time 
to catch up and evict the network manager.

The partial solution here is to synchronize on the network manager list,
stopping the main thread from processing network managers when we are 
preparing our new connection. 

But I suspect the best solution would be to correct the root of the 
problem, and correct the race condition in the server itself. This 
could be done by only adding the network manager when the channel is
active
(see ChannelInboundHandler.channelActive).
2013-12-14 04:05:12 +01:00
Kristian S. Stangeland c568481da3 Correctly clean up the old injection handler. 2013-12-13 21:34:03 +01:00
Kristian S. Stangeland 74d83b3ed6 Add support for the latest build of Spigot.
See commit 8b2b731ea5deda5607058849f2ca9ec2e3bf003f in SpigotMC/
Spigot-Server.
2013-12-13 20:35:00 +01:00
Kristian S. Stangeland e7273385cf Merge pull request #27 from ttaylorr/master
Update Readme.md to include Java and YAML syntax highlighting
2013-12-12 21:57:41 -08:00
Taylor Blau 517eeb9f3f Update Readme.md to include Java and YAML syntax highlighting 2013-12-12 12:02:57 -05:00
Kristian S. Stangeland bec5706e33 Merge pull request #26 from YukonAppleGeek/master
Fix ConcurrentPlayerMap to use SafeCacheBuilder
2013-12-11 03:33:14 -08:00
YukonAppleGeek d166baf721 Fix ConcurrentPlayerMap to use SafeCacheBuilder 2013-12-10 19:18:58 -08:00
Kristian S. Stangeland 922fb94804 Added support for cloing ServerPing packets. 2013-12-11 03:55:25 +01:00
Kristian S. Stangeland ee53fc0d5f Starting to add unit tests for PacketType. 2013-12-10 22:57:43 +01:00
Kristian S. Stangeland 17a5b06577 Adding a new ChunkCoordIntPair wrapper. 2013-12-10 22:35:29 +01:00
Kristian S. Stangeland 745a0846ea Bumping to 3.0.2-SNAPSHOT 2013-12-10 19:43:24 +01:00
Kristian S. Stangeland 726374e7f5 Bumping to 3.0.1 2013-12-10 16:38:33 +01:00
Kristian S. Stangeland 9a07979733 Ensure the new style listener gets the correct GamePhase in 1.6.4.
Also adding in a test image for our ServerPing wrapper.
2013-12-10 16:38:23 +01:00
Kristian S. Stangeland c23e5c98f8 Release of 3.0.0 2013-12-10 16:27:46 +01:00
Kristian S. Stangeland ab0ef6d37c A couple of small last minute bug fixes. 2013-12-10 16:27:30 +01:00
Kristian S. Stangeland 9b0d4294cb Adding a converter for block fields. 2013-12-10 13:21:31 +01:00
Kristian S. Stangeland c3ae43c75f Added wrappers for every enum in the packets. 2013-12-10 12:17:15 +01:00
Kristian S. Stangeland bdc739317b Added a wrapper for ServerPing fields. 2013-12-09 23:09:08 +01:00
Kristian S. Stangeland e8759d0b72 Refactor the wrapper classes so they inherit from a common class. 2013-12-09 17:19:07 +01:00
Kristian S. Stangeland 143ed2ff02 Moving accessor methods to a separate "Accessors" factory class. 2013-12-09 12:03:30 +01:00
Kristian S. Stangeland b70c7fa775 Update the player reference (from TemporaryPlayer) as soon as possible. 2013-12-09 00:02:44 +01:00
Kristian S. Stangeland 154d73ae51 Added the ability to read and modify server-side chat messages.
This introduces the new WrappedChatComponent class, which can be 
accessed through PacketContainer.getChatComponents().
2013-12-08 23:45:35 +01:00
Kristian S. Stangeland 1aaf272878 Don't attempt to inject closed channels. Fixes issue #23 2013-12-08 19:50:59 +01:00
Kristian S. Stangeland 20524c1c3c Don't catch any exceptions in the underlying decoder in ProtocolLib. 2013-12-08 19:44:38 +01:00
Kristian S. Stangeland feae8dd400 Add some debug information to the mssing decoder/encoder exception. 2013-12-08 14:37:50 +01:00
Kristian S. Stangeland be95fbc430 Only patch the encoder if it was found. 2013-12-08 14:34:55 +01:00
Kristian S. Stangeland b947ed1193 Synchronize before attempting to modify thread-safe fields.
We also re-wrap proxied lists in Collections.synchronizedList(). May 
fix ticket #157 on BukkitDev.
2013-12-08 07:47:25 +01:00
Kristian S. Stangeland a74d2ca8bc Prevent cancelled packets from been cancelled over and over again. 2013-12-08 00:51:46 +01:00
Kristian S. Stangeland fa55e2cb27 Remove any references to Guava in Minecraft. 2013-12-07 19:52:02 +01:00
Kristian S. Stangeland 029b19d94c Run onPacketSending() on the main thread if not otherwise stated.
Now onPacketSending() should only be executed on the main thread, 
unless the listener has specified ListenerOption.ASYNC. This option
is off by default, however.

This ensures that older plugins that assume onPacketSending() doesn't
crash the server. They SHOULD use the ASYNC option if possible, as
this explicit synchronization will slow down the STATUS 
protocol somewhat.
2013-12-07 19:14:03 +01:00
Kristian S. Stangeland 4d11cfa8e8 It is an error to pass an empty type array. 2013-12-07 17:39:06 +01:00
Kristian S. Stangeland 54ef43fae8 Added more constructors to PacketAdapter. 2013-12-07 17:31:27 +01:00
Kristian S. Stangeland b6625e6e39 Don't extract a NetworkManager from a temporary player. Fixes #155
Instead, look up its channel injector directly.
2013-12-07 17:22:50 +01:00
Kristian S. Stangeland 8be221ff2e Update the plugin version. 2013-12-07 17:04:48 +01:00
Kristian S. Stangeland e44f02e1fa Reload the previous value in VolatileField when calling refreshValue() 2013-12-07 00:56:45 +01:00
Kristian S. Stangeland d83655f2d3 Add the ability to print out every packet listener. 2013-12-07 00:47:13 +01:00
Kristian S. Stangeland aa3600a337 Don't attempt to add NULL keys to a ConcurrentHashMap. Fixes #21 2013-12-06 23:07:56 +01:00
Kristian S. Stangeland 23ebcb47ab Align the packet types with spaces only, not TABs and spaces. 2013-12-06 21:17:05 +01:00
Kristian S. Stangeland ba064f649f Depreciate everything in PacketTest. 2013-12-06 20:22:05 +01:00
Kristian S. Stangeland 2a0e782725 Merge pull request #19 from DerFlash/testsFix
Stay backwards compatible to Java 6 in test classes
2013-12-06 11:16:46 -08:00
Kristian S. Stangeland e064c1feb4 Adding legacy packets for older versions. 2013-12-06 20:05:46 +01:00
Kristian S. Stangeland 99eda4beca Don't complain about NULLs in network marker. 2013-12-06 18:10:22 +01:00
Kristian S. Stangeland d8df5a7667 Revert renaming "AdapterParameteters" to "AdapterParameters".
Backwards compatibility is more important than spelling.
2013-12-06 18:09:57 +01:00
Kristian S. Stangeland 6159507bb4 Attempting to add backwards compatibility for NetworkMarker. 2013-12-06 18:02:26 +01:00
DerFlash 253a0cf2a6 Stay backwards compatible to Java 6 (getHostString() is available but protected in Java 6)
We could still use this function with Java 6 using reflection, but that would be overkill for tests only ;-)
2013-12-06 08:54:48 +01:00
Kristian S. Stangeland 92c8f21d5e Merge remote-tracking branch 'origin/master' 2013-12-06 06:09:08 +01:00
Kristian S. Stangeland 3509702204 Wups, nothing to see here. 2013-12-06 06:08:52 +01:00
Kristian S. Stangeland be2d6b205b Merge pull request #18 from Devil-Boy/master
Fixed two typos in PacketType (didn't merge properly from thesbros)
2013-12-05 21:02:28 -08:00
Kristian S. Stangeland 8eeb8645e7 Notify users of PacketListener about async server events. 2013-12-06 05:56:02 +01:00
Devil Boy 745c4054a3 Just a tiny correction for GameProfile Javadocs 2013-12-05 19:38:29 -08:00
Devil Boy 7f4f2bb724 Fixed two typos in PacketType (didn't merge properly from thesbros) 2013-12-05 19:25:06 -08:00
Kristian S. Stangeland 84ce20f202 Added a GameProfile wrapper class.
Easy enough, as we don't have to use reflection.
2013-12-06 02:31:50 +01:00
Kristian S. Stangeland 8e9c9951a9 Clear state related to a packet when its resent. 2013-12-06 02:07:42 +01:00
Kristian S. Stangeland d746fcbf6c Merge pull request #17 with spelling corrections from devilboy.
Thanks guys. :)
2013-12-06 01:38:34 +01:00
Kristian S. Stangeland e39310fbcc Use getProperty() instead of System.lineSeperator(). See pull #12
Thanks to DerFlash for notifying me about this issue.
2013-12-06 01:25:33 +01:00
Kristian S. Stangeland 6c3a3e0ba2 Intercept server packets when they're scheduled in the main thread.
Note that some server packets, such as LOGIN and STATUS, will still be 
run asynchronously.
2013-12-06 01:21:35 +01:00
Kristian S. Stangeland 29d71e05e4 Properly cancel written packets. 2013-12-05 21:27:22 +01:00
Devil Boy 7d8d8ead08 Fixed a few trivial spelling errors 2013-12-05 12:24:31 -08:00
Devil Boy d42b4de6e9 Merge remote-tracking branch 'thesbros/master' 2013-12-05 12:04:09 -08:00
Devil Boy a9dbca0bfb Renamed AdapterParmeteters to AdapterParameters
Added a new .param(Plugin, PacketType...) method to replace deprecated
Minor Javadoc edits
2013-12-05 11:55:00 -08:00
Kristian S. Stangeland be6d4fb720 Precreate the inputBuffers set, avoiding a trivial NPE. 2013-12-05 20:51:28 +01:00
Kristian S. Stangeland 348019c1e1 Look for the correct client or server packet when we have the chance.
This should improve backwards compatiblity.
2013-12-05 20:40:45 +01:00
thesbros 9a658065e1 Another 2013-12-05 10:48:42 -08:00
Devil Boy 79c1e81c02 Deprecated a method that was accepting packet IDs
Fixed a small typo
2013-12-04 23:56:49 -08:00
Kristian S. Stangeland 8f408c2109 Rename PacketType members to be more like the new classes in Bukkit 2013-12-05 08:21:42 +01:00
Kristian S. Stangeland f5e519eada Find the correct send packet method. 2013-12-05 08:08:13 +01:00
Kristian S. Stangeland 7afc930592 Increment to 3.0.0.
This is a massive update, so it's warranted. And many plugins will have
to be updated, though mostly because the packets themselves have 
changed.
2013-12-05 06:53:20 +01:00
Kristian S. Stangeland 9052de5316 Switch to using PacketType whenever the new code will be running.
We don't convert legacy code.
2013-12-05 06:52:20 +01:00
Kristian S. Stangeland 6842166b94 Finalize the Netty injection system.
We still have some more debugging and refactoring left, however. In 
particular, I have to switch to PacketType in wherever possible.
2013-12-05 03:54:17 +01:00
Kristian S. Stangeland 7b813fa4e6 Undo mocking in MinecraftReflectionTest when we're done. 2013-12-04 19:22:51 +01:00
Kristian S. Stangeland f94b060591 Ensure StreamSerializer functions correctly in 1.7.2 2013-12-04 19:19:28 +01:00
Kristian S. Stangeland 7055cadaef Adding a test for checking the fallback methods in MinecraftReflection 2013-12-04 17:48:51 +01:00
Kristian S. Stangeland 398b1bc3be Add support for serializing and deserializing NBT tags in 1.7.2 2013-12-04 17:48:17 +01:00
Kristian S. Stangeland 65e665aa59 Don't compare NBT names - they are no longer persistant. 2013-12-04 15:55:17 +01:00
Kristian S. Stangeland c137640fee Updating ProtocolLib for 1.7.2.
Still a work in progress.
2013-12-04 04:17:02 +01:00
Kristian S. Stangeland 28b4874c60 Depreciate the Packets class - it is not safe to rely on integers. 2013-12-01 06:45:59 +01:00
Kristian S. Stangeland c9fd6b4b93 Avoid item stack IDs if we can. 2013-11-23 05:01:52 +01:00
Kristian S. Stangeland 125bfbad30 TemporaryPlayer doesn't support getName(), so use getAddress() for maps
In addition, adding a useful ConcurrentPlayerMap that doesn't store 
player instances directly, and can even handle TemporaryPlayers.
2013-11-23 05:00:34 +01:00
Kristian S. Stangeland 00b297475c Switching to 1.6.4 in the unit tests. 2013-11-19 13:14:47 +01:00
Kristian S. Stangeland 86f04f53b5 Allow packet constructor to properly unwrap other Bukkit wrappers. 2013-11-19 00:50:55 +01:00
Kristian S. Stangeland ba6c6e5abf Improve the documentation of getTypeID().
It might be confused with getIndex(), so emphasize the fact that it 
returns the data type ID of the object, and not its index.
2013-11-18 22:25:05 +01:00
Kristian S. Stangeland 3d6cfbf1e1 Ensure the MinecraftReflection.isCLASS-method return false for NULL. 2013-11-18 17:55:31 +01:00
Kristian S. Stangeland 4be582ef87 Use the MCPC JAR remapper when loading classes. Fixes #11
This will allow plugins to use MinecraftReflection.getMinecraftClass() 
in both CraftBukkit and MCPC.
2013-11-16 03:04:00 +01:00
Kristian S. Stangeland 8c8ca3746b Correctly wrap and unwrap ItemStacks in WrappedWatchableObject.
This should make it possible to set and retrieve watchable objects 
of the type ItemStack.
2013-11-10 20:47:32 +01:00
Kristian S. Stangeland b6809f6ce6 Correctly remove packet listeners from the sorted lists.
Essentially, the SortedCopyOnWriteArray.remove() method would remove 
any packet listeners following a match, instead of just the match 
itself. 

Thanks to Libraryaddict for discovering the bug. :)
2013-11-01 04:45:30 +01:00
Kristian S. Stangeland c416877f35 Add support for retrieving WorldType in MCPC 2013-11-01 02:09:59 +01:00
Kristian S. Stangeland 47134b43cc Adding automatic GamePhase detection.
This should solve the infamous UNKNOWN ORIGIN problem, especially in 
MCPC++.
2013-10-31 01:55:55 +01:00
Kristian S. Stangeland 68c0a3c1dc Ignore fake players in MCPC++ 2013-10-28 19:09:11 +01:00
Kristian S. Stangeland f3b2b0bf3b Remove spigot listener when the plugin is disabling. 2013-10-18 03:45:06 +02:00
Kristian S. Stangeland 3ea5b5d62f Increment to 2.7.4-SNAPSHOT 2013-10-18 03:44:49 +02:00
Kristian S. Stangeland 6a0600cd7f Update to the latest version of Gravity's Updater class. 2013-10-17 02:14:02 +02:00
Kristian S. Stangeland 42395abe75 Add a small hint for plugin authors to the "unknown origin" error. 2013-10-14 19:09:59 +02:00
Kristian S. Stangeland 73804d45df Make UnknownHostException more low-key. They are to be expected.
Whenever the update procedure is ongoing, and the server temporarily 
lost connectivity to the internet (or more likely, dev.bukkit.org is 
down), a huge exception log will be printed in the console. See ticket
134 for a real life example.

This is completely out of propotion to the problem at hand (connection
problems), so we will replace the error log with a simple warning 
message.
2013-10-11 02:49:07 +02:00
Kristian S. Stangeland 8a4982e169 Add the test plugin to the plugin manager, just in case. 2013-10-07 21:50:07 +02:00
Kristian S. Stangeland c058498267 Add a timeout to server startup and the ping test. 2013-10-07 05:16:20 +02:00
Kristian S. Stangeland 09cc024a3f Added a simple integration test to ensure ProtocolLib actually works.
We do this by running a CraftBukkit server in target/server, and 
copying ProtocolLib to its plugin folder.
2013-10-07 04:29:21 +02:00
Kristian S. Stangeland 47ffa7f62d Bumping to version 2.7.3 2013-10-06 02:35:07 +02:00
Kristian S. Stangeland c2abc4277b Fix a critial bug that prevents PB from loading.
This was caused by a previous patch that corrected the version parser 
for special Spigot versions with a snapshot protocol enabled. 
Unfortunately, this also causes the comparer to fail on normal 
version ...

Somehow, that got through testing.
2013-10-06 02:34:33 +02:00
Kristian S. Stangeland e4c193440e Correct all JavaDoc problems. 2013-10-05 21:42:19 +02:00
Kristian S. Stangeland b1b4bc7fb3 Bumping to version 2.7.2 2013-10-05 20:20:43 +02:00
Kristian S. Stangeland a63e03bf16 Version 1.6.4 is working fine. 2013-10-05 20:20:08 +02:00
Kristian S. Stangeland b53908ef0f These fields will be updated and read from different threads.
Mark them as volatile just in case.
2013-09-30 19:27:22 +02:00
Kristian S. Stangeland 75f6cff5a5 Experimental: Correct a possible race condition with Spigot. 2013-09-28 17:22:06 +02:00
Kristian S. Stangeland 1b1f36c5d6 Attempt to handle snapshot versions by assuming Minecraft version.
The snapshot version contains a release date, so we'll simply compare
that against a known release date of a Minecraft version. It it's 
later, we know it is at least a minor version above, and vice versa.
2013-09-28 16:25:30 +02:00
Kristian S. Stangeland 2001c15132 Add the ability to track the amount of time spent by each plugin.
ProtocolLib can now keep track of the amount of time spent by each 
listener (for each packet), generated as a report file. This is 
done in the new "protocol timings" command.
2013-09-20 22:35:59 +02:00
Kristian S. Stangeland 4e2af45428 More descriptive error message when an entities tracker is not found. 2013-09-15 20:42:07 +02:00
Kristian S. Stangeland bed74f6ab6 Cleanup proxy packet injector in Spigot too. FIXES 127 2013-09-13 09:35:53 +02:00
Kristian S. Stangeland 57ad8d8aaa Lets preserve the injection time, just in case. 2013-09-10 02:15:16 +02:00
Kristian S. Stangeland dc8b23d48b Intercept packets sent during the handling of PlayerLoginEvent. 2013-09-10 01:48:38 +02:00
Kristian S. Stangeland 4392eb9ea0 Don't choke on exceptions during error filtering. Pointless. 2013-09-05 04:07:05 +02:00
Kristian S. Stangeland 9442e2c36f Report using the correct sender in the update methods. 2013-09-05 04:03:01 +02:00
Kristian S. Stangeland db2efbe089 Relax expected Minecraft version format. Partially fixes ticket 124 2013-09-02 16:14:11 +02:00
Kristian S. Stangeland d810d17abb Increment to 2.7.1-SNAPSHOT for development towards the next version. 2013-09-02 16:04:55 +02:00
Kristian S. Stangeland 37766d3ecf Added some missing features to 2.7.0.
Note that this supercedes the previous increment commit.
2013-09-02 02:42:18 +02:00
Kristian S. Stangeland 1b5463651f Increment to 2.7.0 2013-09-02 02:00:14 +02:00
Kristian S. Stangeland fba2b7a6b3 Add the ability to broadcast a packet to every player or nearby players
This is just a convenience method.
2013-09-01 18:47:24 +02:00
Kristian S. Stangeland 8295b951d9 Don't throw an exception the first time we initialize IntHashMap. 2013-08-29 23:57:20 +02:00
Kristian S. Stangeland fc01a61290 Call the correct get-method (not remove) in IntHashMap. Fixes #7
As it happens, there are two very similar methods in IntHashMap:
  Object get(int key);
  Object remove(int key);
  
I called the latter by mistake. Now, I distinguish between the two 
by performing a removal test.
2013-08-29 01:33:43 +02:00
Kristian S. Stangeland 3f8a48732e Don't assume the InputStream is an DataInputStream in Spigot's case. 2013-08-27 23:23:30 +02:00
Kristian S. Stangeland be9bbc924e Experimental fix for issue #7 on Github.
Attempt to detect the correct "get" method in a IntHashMap.
2013-08-27 22:09:06 +02:00
Kristian S. Stangeland 3923e05178 Asynchronous client packets are handled twice. FIXES 118
This causes wierd artifacts such as double placing of half-slabs and so
on, as Minecraft will process the packets again (except chat packets). 
We correct this by uncancelling the asynchronous packet, and then 
cancelling it again.
2013-08-15 01:04:56 +02:00
Kristian S. Stangeland ccc123b26f Added missing server packet WORLD_PARTICLES. 2013-08-09 23:15:33 +02:00
Kristian S. Stangeland ba692d5b7d Display whether or not a packet has been cloned in the packet command. 2013-08-07 00:33:57 +02:00
Kristian S. Stangeland b27922dd3d Correctly clone enum set. 2013-08-07 00:33:23 +02:00
Kristian S. Stangeland 18f5998f8a Correctly clone packet listeners. 2013-08-06 20:41:25 +02:00
Kristian S. Stangeland 65f1371cf4 Don't create a network marker unnecessarily. 2013-08-06 19:44:38 +02:00
Kristian S. Stangeland 50c2931484 ItemStacks that represents AIR cannot store NMS tags. Use exceptions. 2013-08-06 19:27:58 +02:00
Kristian S. Stangeland 6d152707cf Increment to 2.6.1-SNAPSHOT for development towards the next version. 2013-08-01 00:49:15 +02:00
Kristian S. Stangeland 8e136579b3 Increment to 2.6.0.
This is due to the inclusion of a new attribute API.
2013-07-31 23:21:44 +02:00
Kristian S. Stangeland 6fba5cb020 Don't reset the ReportType static fields. This is pointless.
This may prevent certain problems on MCPC+.
2013-07-31 05:21:16 +02:00
Kristian S. Stangeland 988026611c Added support for modifying attributes in UPDATE_ATTRIBUTES.
This contains a fully-fledged API for reading and modifying Attribute-
Snapshot and AttributeModifier. Keep in mind that these objects are 
immutable, so modification must be made through object builders.

The packets are also shared, so packet cloning might be necessary if 
attributes should differ per player.
2013-07-28 02:02:27 +02:00
Kristian S. Stangeland 7170bfcadc Only add the WorldType converter if it exists in that Minecraft version 2013-07-26 18:42:06 +02:00
Kristian S. Stangeland 5c2fc8684e Use the object wrappers when printing custom Minecraft objects. 2013-07-26 17:30:47 +02:00
Kristian S. Stangeland 34f5278605 Improve support for the toString() method in wrapped objects. 2013-07-26 05:04:59 +02:00
Kristian S. Stangeland 5584357fee Pass on the "filtered" parameter in sendServerPacket. FIXES Ticket-111
Looks like our good ol' friend copy-paste betrayed us yet again. I need 
more unit tests.
2013-07-26 03:55:36 +02:00
Kristian S. Stangeland 260f108749 Don't complain about plugins that does their own dependency management
Let me know if I should add your plugin to the exception list.
2013-07-25 12:51:41 +02:00
Kristian S. Stangeland 3b76fe3d99 Increment to 2.5.1-SNAPSHOT for development towards the next version. 2013-07-22 17:24:16 +02:00
Kristian S. Stangeland d116be6fd4 Bumping to version 2.5.0. 2013-07-22 16:46:19 +02:00
Kristian S. Stangeland 51879afe07 Updating Eclipse settings. 2013-07-22 16:45:11 +02:00
Kristian S. Stangeland d70f102655 Add a getEntityModifier() overload that makes it easier to use.
Most people use it to retrieve the entity referenced in a certain
packet, which will always be in the world of the receiving player. This
method makes the same assumption.
2013-07-21 03:23:29 +02:00
Kristian S. Stangeland 5f54b95998 Fix getEntityModifier() for MPCP 1.6.2. 2013-07-21 03:20:13 +02:00
Kristian S. Stangeland 6054d559e1 Add support for the new JSON chat message format. FIXES Ticket-109
In addition, the PacketConstructor can now handle non-wrapped objects,
along with Class types instead of instances in withPacket().
2013-07-19 23:43:39 +02:00
Kristian S. Stangeland 0b56df20d5 Add a getMinecraftVersion() method to the ProtocolManager.
This can be useful if you want to implement different read/write 
methods for each version of Minecraft.
2013-07-19 21:22:34 +02:00
Kristian S. Stangeland 458a92c34a Added missing open tile entity packet. 2013-07-19 20:03:00 +02:00
Kristian S. Stangeland 51c645928f Make it possible to specify optionIntercept() when using BOTH. 2013-07-19 19:47:22 +02:00
Kristian S. Stangeland 7b9a5e9db4 Make the clientSide/serverSide builder options additative. 2013-07-19 19:41:38 +02:00
Kristian S. Stangeland 2756b80ac0 Add a more beatiful builder pattern to PacketAdapter.
We can't use a traditional builder pattern though, as it's commonly 
used for constructing inner anonymous classes. But we can do the next 
best thing and create a builder for the parameters themselves.

Here's what the new syntax looks like:
  new PacketAdapter(params(this, 1, 2, 3).clientSide()) {
      @Override
      public void onPacketReceiving(PacketEvent event) {
          // Your code here
      }
  };
  
Where "this" referes to your plugin.
2013-07-19 03:46:51 +02:00
Kristian S. Stangeland 62094492a9 Make it possible to intercept and expand upon the unknown origin error.
We add another configuration option that enables stack traces of 
warnings.
2013-07-19 02:27:13 +02:00
Kristian S. Stangeland 92781be618 Add support for SportBukkit. FIXES Ticket-107 2013-07-18 22:36:01 +02:00
Kristian S. Stangeland ce2079b0bc Bumping to 2.5.0-SNAPSHOT. 2013-07-17 23:03:16 +02:00
Kristian S. Stangeland 1e4a03b056 Minecraft 1.6.2 seems to work fine. 2013-07-17 23:02:45 +02:00
Kristian S. Stangeland 7f69c41bf6 Don't pass our CaptureInputStream to the "get player identity" function 2013-07-17 23:02:16 +02:00
Kristian S. Stangeland a31dc6fdcb Handle NULL results from the deserialization methods. 2013-07-17 21:31:44 +02:00
Kristian S. Stangeland 9448e06ad8 Add the ability to store an NBT compound in a given ItemStack. 2013-07-17 20:59:09 +02:00
Kristian S. Stangeland aa9d84c639 Added the ability to serialize and deserialize NbtCompounds. 2013-07-17 20:31:25 +02:00
Kristian S. Stangeland b0cec61d66 Added more utility methods to the stream serializer class.
It now supports the default Minecraft string encodign (UTF-16).
2013-07-17 20:07:02 +02:00
Kristian S. Stangeland 9f0d3a5054 Added the ability to retrieve the input buffer as a stream. 2013-07-17 19:49:00 +02:00
Kristian S. Stangeland fefad5d806 Cleanup the write-packet method interceptor. 2013-07-17 19:40:48 +02:00
Kristian S. Stangeland 35d5912275 Rename to SerializableCloner. 2013-07-17 15:49:35 +02:00
Kristian S. Stangeland e7aa1ca98c Merge branch 'master' of https://github.com/aadnk/ProtocolLib.git 2013-07-17 07:28:36 +02:00
Kristian S. Stangeland 6554a34752 Ensure that the new read packet feature is supported in Spigot. 2013-07-17 07:27:03 +02:00
Kristian S. Stangeland 6527c0a7f5 Added the ability to intercept serialized input packets on CraftBukkit.
Spigot will be added later.
2013-07-17 06:46:34 +02:00
Kristian S. Stangeland 8f30199a22 Adding a packet handler adapter, for symmetri with PacketAdapter. 2013-07-17 05:52:20 +02:00
Kristian S. Stangeland e25ab31316 Generate a new class for each packet type, as getID is final.
We can't override the getID method in Packet, as it is marked as final.
Instead, we'll just generate a seperate class for each packet ID that 
needs to be intercepted.

We can't inherit from the packet ID of any particular class, as it 
may get recognized and modified by the sendPacket() method. This could
be a problem if this recogition is necessary, but we'll come back to 
this later.
2013-07-17 05:48:34 +02:00
Kristian S. Stangeland 051a4eda87 Add the ability to intercept the write method of packets.
This is done by constructing a proxy around the class after every
event handler has been invoked, intercepting the write method. Each 
PacketOutputHandler registered by the packet event listeners is 
invoked in turn, modifying a byte array of the data that will be 
written to the network stream.

The byte array is initially filled with the serialized version of the 
packet in the packet event.
2013-07-17 03:52:27 +02:00
Kristian S. Stangeland 45f36f9ad6 Use reflection to clone certain packets (44 - update attributes).
These packets contain objects (AttributeSnapshot and 
AttributeModifiable) isn't supported by the current cloning system.
2013-07-11 21:51:51 +02:00
Kristian S. Stangeland 468d013032 Remove JavaDoc - that should be in gh-pages. 2013-07-11 20:58:16 +02:00
Kristian S. Stangeland cd0f8a6fa5 Merge branch '1.6.2' 2013-07-11 00:12:45 +02:00
Kristian S. Stangeland f2125623f6 Correct the fallback method for retrieving WatchableObjects. 2013-07-11 00:04:42 +02:00
Kristian S. Stangeland 1000378b78 Update packet container serialization for Minecraft 1.6.2. 2013-07-10 23:41:49 +02:00
Kristian S. Stangeland c590e4a825 Update the stream serializer for Minecraft 1.6.2 2013-07-10 23:33:25 +02:00
Kristian S. Stangeland a4eb219a9a Update unit tests to CraftBukkit 1.6.2 2013-07-10 22:45:20 +02:00
Kristian S. Stangeland 6fe7fe46f3 Use a builder pattern instead of a constructor with 8 parameters.
Also make use of the fact that Spigot may have initialized its 
server connection (in the latest version).
2013-07-07 14:03:07 +02:00
Kristian S. Stangeland fb7f80b646 Fix typo before it's too late. 2013-07-07 11:26:10 +02:00
Kristian S. Stangeland 00ff832e07 Adding the two new packets for 1.6.1. 2013-07-06 16:32:42 +02:00
Kristian S. Stangeland 47a5382709 Properly handle reloads on Spigot.
The code is getting uglier and uglier ...
2013-07-06 08:06:29 +02:00
Kristian S. Stangeland a4f81e5e9f Update asynchronous manager and handle static senders in reports. 2013-07-06 07:51:02 +02:00
Kristian S. Stangeland 6847283fb3 Added the Comphenix Maven repository. 2013-07-06 00:25:59 +02:00
Kristian S. Stangeland 5e35f46b96 Added support for Spigot with Netty disabled.
I've also added BukkitExecutors to ProtocolLib, which any plugin that
depend on ProtocolLib may now use.
2013-07-06 00:24:11 +02:00
Kristian S. Stangeland 81e158d74a Increment to 2.4.8-SNAPSHOT for development towards the next version. 2013-07-05 06:23:40 +02:00
Kristian S. Stangeland 1159b4541e Increment to 2.4.7 for Minecraft 1.6.1 support. 2013-07-05 06:14:57 +02:00
Kristian S. Stangeland 49eb39e45f Improve packet class lookup performance by mainaining a inverse map. 2013-07-04 01:12:39 +02:00
Kristian S. Stangeland 0ec2a705da Mark 1.6.1 as tested. Things seems to work now. 2013-07-02 17:42:51 +02:00
Kristian S. Stangeland 5e5243e3fb Update ProtocolLib for 1.6.1.
A lot of methods changed from accepting DataInputStream to DataInput, 
which messed with the part that dynamically finds the "readPacket" 
method. Changed to accepting any method whose signature contains a 
parameter derived from DataInput.
2013-07-02 17:41:43 +02:00
Kristian S. Stangeland 0b3fe5470a Added a plugin verifier test.
Attempting to solve a problem discovered by Silthus, but I need
more information.
2013-06-27 03:05:53 +02:00
Kristian S. Stangeland ef4476a72e Print a warning message instead of crashing.
We shouldn't prevent a plugin from adding packet listeners just because
the plugin verifier failed.
2013-06-23 21:56:53 +02:00
Kristian S. Stangeland 8d0e8139de Add support for the Lilypad Bukkit Connector. FIXES Ticket-101 2013-06-23 21:38:35 +02:00
Kristian S. Stangeland 256581d4ed Incrementing to 2.4.6-SNAPSHOT 2013-06-23 21:35:00 +02:00
Kristian S. Stangeland 56b81281a8 Merge branch 'master' into gh-pages 2013-06-21 01:12:07 +02:00
Kristian S. Stangeland f7aa146a94 Increment to 2.4.5 2013-06-21 00:46:16 +02:00
Kristian S. Stangeland a797dba594 Fixed a memory leak on Spigot that would retain unloaded worlds. 2013-06-20 01:06:50 +02:00
Kristian S. Stangeland 2cce1bca0a Certain packets must be cloned. Document this. 2013-06-19 23:19:46 +02:00
Kristian S. Stangeland 8f2935e241 Minimize the number of unnecessary references. 2013-06-19 02:07:12 +02:00
Kristian S. Stangeland e7954a0f79 Load JavaScript engine on demand. 2013-06-17 13:16:29 +02:00
Kristian S. Stangeland 169842f265 Ignore logged out players. 2013-06-14 03:52:31 +02:00
Kristian S. Stangeland 627c500de1 Added some debug information when a network manager lacks an address. 2013-06-01 01:32:24 +02:00
Kristian S. Stangeland aa5e1beb7f Added the ability to suppress arbitrary warnings and errors. 2013-06-01 00:17:08 +02:00
Kristian S. Stangeland 77346f8438 Fixed incorrect detection of "custom" net handlers.
This should ensure that users on MCPC no longer recieves the 
REPORT_DETECTED_CUSTOM_SERVER_HANDLER warning just for using MCPC.
2013-05-31 22:57:00 +02:00
Kristian S. Stangeland bbe766cf23 Bumping to 2.4.4-SNAPSHOT for development towards the next version 2013-05-31 22:51:36 +02:00
Kristian S. Stangeland 542105601b Updating JavaDoc for 2.4.3 2013-05-14 01:29:12 +02:00
Kristian S. Stangeland 7e20abdd37 Merge branch 'master' into gh-pages
Conflicts:
	ItemDisguise/.settings/org.eclipse.core.resources.prefs
2013-05-14 01:24:47 +02:00
Kristian S. Stangeland 9b8d61b2b0 Added small settings file in ItemDisguise 2013-05-14 01:19:22 +02:00
Kristian S. Stangeland b613e8f01e Bumping to 2.4.3 2013-05-14 01:17:03 +02:00
Kristian S. Stangeland 02b5dec304 Permit cross edges when validating dependencies. FIXES 91. 2013-05-13 03:49:48 +02:00
Kristian S. Stangeland 6af440789c Invert the actual packet instance instead of a new instance.
In NetworkFieldInjector, we use a "inverted" packet class to undo the 
data received counter when a packet has been cancelled. 

Previously, we would generate a proxy class that inherits from the 
class of the packet (Packet3Chat, etc.) along with its size() method 
(which is called and added to the counter, cancelling the other packet),
but this doesn't work for packets that are dynamically sized such as
Packet255KickDisconnect. Instead, we now pass the actual instance to
 the proxy class through a weak hash map.
2013-05-07 23:11:41 +02:00
Kristian S. Stangeland a2b04e055a Make it possible to cancel packets for asynchronous processing. 2013-05-06 18:57:19 +02:00
Kristian S. Stangeland 8d814d2d9c Implement "isOnline" for temporary players.
This corrects the issue seen on http://pastebin.com/C4D8jsja
2013-05-06 18:37:08 +02:00
Kristian S. Stangeland 0fc6396974 ProtocolLib seems to work fine for 1.5.2 too. 2013-05-05 23:55:02 +02:00
Kristian S. Stangeland c813811721 No point verifying the load order for ProtocolLib.
Also removed a debug message.
2013-04-30 03:17:59 +02:00
Kristian S. Stangeland 8970cd915a Move the last update time stamp to a separate file. FIXES TICKET 86. 2013-04-29 16:39:37 +02:00
Kristian S. Stangeland 8964246e22 Makes more sense to put this in the reflect lookup. 2013-04-28 17:27:58 +02:00
Kristian bec05967d3 Mark field as volatile. 2013-04-27 02:48:39 +02:00
Kristian cecf80250c Don't print pointless warnings on Spigot. 2013-04-27 02:24:49 +02:00
Kristian 93da193428 Improve JavaDoc. 2013-04-27 01:03:20 +02:00
Kristian 40a3abf5b9 Refactor the report system. Allow identification of report messages.
All warnings and error messages will now be identified using fields in 
the sender classes, to avoid depending on the format of the error or 
warning messages directly. This decoupling will make it possible to 
filter out certain irrelevant messages.
2013-04-26 20:59:28 +02:00
Kristian S. Stangeland 7e18207a2b Load at startup to stay compatible with startup plugins.
Earlier versions of ProtocolLib would in fact load at startup, but due
to an experimental change (ffd920e5b2) -
where ProtocolLib injected code into DedicatedServerConnectionThread in
order to intercept pending connections - the load order had to be
set to world.

This injection was later removed, but the load order was never 
reinstated. This causes problems with plugins that load on startup, 
but also depend on ProtocolLib as load order trumpts dependency.

This change reintroduces startup load order.
2013-04-20 19:26:54 +02:00
Kristian S. Stangeland b451a5a672 Adding a plugin verification system detecting common programmer errors
Initially we will detect plugins that attempt to register a listener in 
ProtocolLib without setting "depend" or "soft-depend".
2013-04-19 21:34:34 +02:00
Kristian S. Stangeland 5ca29ef5ce Leave ThreadDead alone!
The original intent of catching throwable is to "sandbox" arbitrary
plugin logic and prevent it from ever accidentally killing threads on
the server. A LinkageError due to a missing or old dependency shouldn't
bring down the server, so we secure it by catching all exceptions around
plugin event handlers.

Trouble is, this also catches exceptions such as OutOfMemoryError or
ThreadDead, which assuredly should NOT be caught. The latter case has
even occured in the wild as seen by ticket 45 of TagAPI on BukkitDev.
Minecraft may terminate the reader and writer thread by calling stop(),
and this could occur within the event handler in a plugin. So we should
let ThreadDead go and propagate it to the appropriate handler in
Minecraft.
2013-04-14 03:53:27 +02:00
Kristian S. Stangeland 37dd46432a Ignore these errors again. 2013-04-13 15:27:31 +02:00
Kristian S. Stangeland cd78e311d5 Looks like Rhino is not guaranteed after all. Add some checks. 2013-04-13 14:20:35 +02:00
Kristian S. Stangeland 8a5ebb88e0 Added the ability to recover from engine incompatibilities. 2013-04-12 17:12:42 +02:00
Kristian S. Stangeland 2411d29822 Correctly reference v1_5_R2. 2013-04-12 05:09:05 +02:00
Kristian S. Stangeland 8e11a40662 Update unit test to 1.5.1 2013-04-11 22:51:44 +02:00
Kristian S. Stangeland bda4474d62 Update packets to 1.5.1 2013-04-11 22:51:34 +02:00
Kristian S. Stangeland 95603d3fa2 Increment to 2.4.2-SNAPSHOT 2013-04-11 22:51:20 +02:00
Kristian S. Stangeland e7be3be17f Increment to 2.4.1 2013-04-09 16:49:17 +02:00
Kristian S. Stangeland 72172805ba Exploit the internal JavaScript parser to determine if the exp is done.
The original code attempted to parse the JavaScript as it went along, 
counting open and close brackets. Unfortunately, this doesn't
take comments and string literals into consideration, so it would very
likely have failed with more complicated filters.

Instead, we'll let the JavaScript compiler handle all the complexity 
and simply see if the code compiles. If it doesn't, but the error 
occured in the last line, we assume it can be recovered by adding a 
new line.
2013-04-09 16:48:26 +02:00
Kristian S. Stangeland da7a58fb43 Incrementing to 2.4.0.
This is due to the non-breaking API changes
in 505226f8ad
2013-04-09 15:11:43 +02:00
Kristian S. Stangeland 82bb7a7c43 Adding support for Spigot MCPC 1.2.5.
Very buggy indeed.
2013-04-08 21:53:54 +02:00
Kristian S. Stangeland 505226f8ad Added the ability to match derived classes and interfaces. 2013-04-08 17:57:56 +02:00
Kristian 867afe29f7 Minecraft 1.5.1 seems to work fine. 2013-04-07 15:57:38 +02:00
Kristian 3ee38d7b6d Arbitrary code execution is very dangerous. Limit to debug mode.
The filter command allows users with sufficient permission (or OPs) to
execute arbitrary JavaScript (no sandboxing). This is fine for a
debug and testing, but could potentially be exploited in a
production environment. 

Instead, we disable this command by default and force users to enable it
specifically in the configuration file (not through commands). If
someone has access to the config.yml file, they probably also have
access to the plugins/ folder and thus the ability to install plugins
with arbitrary code execution as well.
2013-04-07 15:57:01 +02:00
Kristian 15980d70fb Added a simple filter system that utilizes JavaScript (Rhino)
This makes it possible to filter packet events with arbitrary code.
2013-04-07 15:33:19 +02:00
Kristian S. Stangeland 5720994a31 Merge remote-tracking branch 'origin/master' 2013-04-02 14:30:14 +02:00
Kristian S. Stangeland 22beae23e0 Retrieve the correct Nbt base class 2013-04-02 14:28:34 +02:00
Kristian S. Stangeland fb441b4910 Retrieve the correct Minecraft superclass 2013-04-02 14:12:36 +02:00
Kristian S. Stangeland 9a34036d14 Improve Minecraft class detection 2013-04-02 13:55:18 +02:00
Kristian Stangeland 27da638a91 Update the player instance on login (LOWEST), not HIGHEST.
Should ensure that packet listeners recieve the most up-to-date player
instance, regardless of whether or not the main thread is blocked in the
player listener. No more temporary players.
2013-03-28 22:27:58 -04:00
Kristian Stangeland b3322b35c1 Prevent ConcurrentModifcationExceptions.
See http://pastebin.com/UBvGSFs9/
2013-03-28 21:58:06 -04:00
Kristian S. Stangeland a43428c2c4 No need to create the dependency reduced POM 2013-03-23 15:45:57 +01:00
Kristian S. Stangeland d643690eef Added a small patch for Libigot. 2013-03-21 02:10:30 +01:00
Kristian S. Stangeland 310fd18e89 Ignore players that have logged out and have not been injected. 2013-03-17 23:55:08 +01:00
Kristian S. Stangeland da0bcaa482 Seems to pass all the preliminary tests. 2013-03-13 23:59:13 +01:00
Kristian S. Stangeland ed9b61fd11 Use an atomic reference array instead of ConcurrentHashMap for listeners 2013-03-12 02:33:35 +01:00
Kristian S. Stangeland e3cfa45607 Ensure that monitor listeners cannot modify a packet event. 2013-03-12 02:02:36 +01:00
Kristian S. Stangeland 1bd0db20ce Invoke MONITOR listeners, even if we are bypassing normal listeners. 2013-03-12 01:16:07 +01:00
Kristian S. Stangeland 3c97cffc09 After Minecraft 1.4.4, CraftBukkit no longer redirects MAP_CHUNK.
We can therefore relax the requirements in NetworkFieldInjector and
NetworkObjectInjetor.
2013-03-12 00:52:09 +01:00
Kristian S. Stangeland a798147e71 Correctly override SpoutPlugin's sendPacket method. 2013-03-10 18:52:41 +01:00
Kristian S. Stangeland e8f5bce3d5 Update JavaDoc for 2.3.0 2013-03-05 17:07:09 +01:00
Kristian S. Stangeland ef334aff50 Merge branch 'master' into gh-pages 2013-03-05 16:59:29 +01:00
Kristian S. Stangeland 9b3893b807 Useless changes. 2013-03-05 16:59:22 +01:00
Kristian S. Stangeland 4406cdb571 Increment to version 2.3.0 2013-03-05 16:51:08 +01:00
Kristian S. Stangeland 9a16143c89 Small documentation fix. 2013-03-05 16:50:59 +01:00
Kristian S. Stangeland 9f6b4b60e3 Don't overwrite an existing player injector in net login. 2013-03-04 16:54:54 +01:00
Kristian S. Stangeland b1b6e9ec20 In blocking hash map, don't remove locks if the value has been replaced 2013-03-04 13:29:46 +01:00
Kristian S. Stangeland 6019ab177c Create a dummy injector if we haven't yet injected the player. 2013-03-04 00:44:09 +01:00
Kristian S. Stangeland d387b2d792 Improve client packet interception by about 37%. 2013-03-03 14:49:20 +01:00
Kristian S. Stangeland 2985dc9cf8 Be less picky over what packets to subclass. FIXES Ticket-49 2013-02-28 22:30:37 +01:00
Kristian S. Stangeland 8388a77f0b Catch ConnectExceptions - they are not serious. 2013-02-28 22:07:53 +01:00
Kristian S. Stangeland 2e0acd3a78 Increment version to 2.2.4-SNAPSHOT 2013-02-28 02:15:22 +01:00
Kristian S. Stangeland d60ab3e953 Identify player connections by socket address.
It's the only thing that will not not be removed when a
network manager closes, making it relatively safe to block on.
2013-02-28 01:39:49 +01:00
Kristian S. Stangeland fbfbd28bea We might revert this some other time, if we need to support other JVMs 2013-02-27 01:15:06 +01:00
Kristian S. Stangeland c7737ca96e It's an injector container. 2013-02-27 01:10:21 +01:00
Kristian S. Stangeland c32d225ef3 Use socket as key instead of input stream. 2013-02-27 01:09:22 +01:00
Kristian S. Stangeland 56807cbd3a Don't throw a NullPointerException if the injection fails. 2013-02-26 19:14:40 +01:00
Kristian S. Stangeland 3357fd6c9c Fix NPE in CleanupStaticMembers.
Determine if this is really necessary. No point cleanup up after
yourself in a sewer.
2013-02-26 13:37:33 +01:00
Kristian S. Stangeland 7968f1ce4f Incremented to 2.2.3-SNAPSHOT 2013-02-26 01:24:20 +01:00
Kristian S. Stangeland 575248063d Attempt to correct a NPE due to a NULL socket. 2013-02-26 01:23:54 +01:00
Kristian S. Stangeland 2cf265f8e8 Switch to a better InputStream -> Socket lookup for normal JVM. 2013-02-25 22:12:18 +01:00
Kristian S. Stangeland 9195e677ab Essentially, it's a lookup for a player's input stream.
Or: InputStream -> PlayerInjector -> Player.
2013-02-25 20:32:27 +01:00
Kristian S. Stangeland 8c2f6bddd8 Moved classes that inject into the server thread to a separate package 2013-02-25 20:28:57 +01:00
Kristian S. Stangeland 89d2604ce2 We cannot support plugin reloaders (PlugMan, PluginManagers).
This is because multiple plugins depend on us, and are not properly 
notified after ProtocolLib has been reloaded.

The only possible solution is to reload every dependent plugin after 
ProtocolLib has been reloaded, but unfortunately, I ran into
LinkageErrors when I tried it. So it's probably not possible with the
current architecture to support reloaders.

Instead, we'll simply print a BIG BOLD warning telling any users of
these plugins that ProtocolLib cannot be reloaded except through the
built in "/reload" command.
2013-02-25 14:38:53 +01:00
Kristian S. Stangeland df4542017a Increment to 2.2.2-SNAPSHOT 2013-02-25 02:00:15 +01:00
Kristian S. Stangeland ffd920e5b2 Experimental: InputStream -> Socket lookup by intercepting accept().
Previously, we have used a BlockingHashMap to simply lock the packet
read thread until we have had a chance to intercept the
NetLoginHandler/PendingConnection and store InputStream ->
PlayerInjector -> TemporaryPlayer. 

Problem is, this could potentially cause problems if, for some reason, a
packet is intercepted after the player has logged out and the player
injector has been removed from the lookup map. In that case, the read
thread would wait until it reaches the default timeout of 2 seconds.
Locking threads is fairly inefficient in general, and waiting for the
server connection thread to update the NetLoginHandler list could take a
while.

Instead, ProtocolLib will now intercept any Socket accepted in the
server's main ServerSocket, and record any calls to getInputStream().
That way, we can get a InputStream -> Socket mapping before the server
thread ever creates the read and write threads in NetLoginHandler ->
NetworkManager.

Unfortunately, it's not trivial to swap out the ServerSocket in the
DedicatedServerConnectionThread - we actually have to trigger the
accept() thread and move through a cycle of the loop before our custom 
ServerSocket is used. To do this, we will actually connect to the server
and read its MOTD manually, hopefully getting to it before any other
players. 

This creates a slight overhead of a couple of threads per server start,
but it's probably much better than locking the read thread. More testing
is needed though before this can be confirmed.
2013-02-25 01:59:48 +01:00
Kristian S. Stangeland 9b0fe540c2 Fixed a couple of bugs discovered by FindBugs. 2013-02-19 17:25:59 +01:00
Kristian S. Stangeland bf443be0d3 Add the ability to read arbitrary objects.
Perhaps NbtBase shouldn't have implemented getValue() after all - it
would have been better to have a shared base interface with getName()
and getType(), and only let the primitive elements implement getValue().

Too late to change it now though.
2013-02-18 15:49:30 +01:00
Kristian S. Stangeland 61ae40b936 NbtCompound can now accepts arbitrary primitive, list or map objects. 2013-02-18 13:27:41 +01:00
Kristian S. Stangeland e919056f9b Provide a remove method in NbtCompound. Discourage getValue().
Added a missing remove method in NbtCompound. In addition, the
getValue() method in NbtCompount has been depreciated. It is far better
to use the put and get methods in NbtCompound instead.
2013-02-17 02:32:14 +01:00
Kristian S. Stangeland 8c0a671078 Correctly print the content of map objects in packets. 2013-02-16 12:33:14 +01:00
Kristian S. Stangeland 3ae10d9123 Retry again if the hack isn't ready. 2013-02-14 20:14:34 +01:00
Kristian S. Stangeland b8b39b4785 Set a maximum perm gen space usage for our background compiler. 2013-02-10 15:23:12 +01:00
Kristian S. Stangeland 0a3c1b13ed Add the ability to load from the default package as well. 2013-02-09 23:22:33 +01:00
Kristian S. Stangeland 02d0a9afd5 Add some debug information to a OutOfMemory exception. 2013-02-09 16:50:42 +01:00
Kristian S. Stangeland dd3cd6c768 Fix an NPE reported in ticket-38. 2013-02-09 13:19:36 +01:00
Kristian S. Stangeland bc1955bff3 Don't attempt to clean up static members during an update. 2013-02-07 20:07:33 +01:00
Kristian S. Stangeland 32282bbe9f Fixed a bug overwriting the NetLoginHandler class with NetServerHandler 2013-02-07 00:23:22 +01:00
Kristian S. Stangeland 351dc7f3df Ensure that ProtocolLib discoveres mod-specific packets. 2013-02-06 22:40:17 +01:00
Kristian S. Stangeland 224b5d7f3e Increment to 2.2.1-SNAPSHOT for development towards the next version 2013-02-06 22:09:37 +01:00
Kristian S. Stangeland 4d17a6a5bb Updating JavaDoc for 2.2.0 2013-02-05 23:35:15 +01:00
Kristian S. Stangeland 52adb8b9fd Merge branch 'master' into gh-pages 2013-02-05 23:32:34 +01:00
Kristian S. Stangeland 75eac1c9aa Improve documentation. 2013-02-05 23:28:27 +01:00
Kristian S. Stangeland 28b213a419 Ensure that the dummy objects are all package private. 2013-02-05 23:28:11 +01:00
Kristian S. Stangeland fc44ff9fba Merge branch 'master' into gh-pages 2013-02-05 22:16:42 +01:00
Kristian S. Stangeland cf52d6ddb4 Bumping to version 2.2.0.
- Adding support for Spigot
- Adding support for MCPC
- Range of bug fixes
2013-02-05 22:10:34 +01:00
Kristian S. Stangeland f3ad0d86fe Increment to pre-release version 2.1.2-SNAPSHOT 2013-02-05 17:53:27 +01:00
Kristian S. Stangeland bdcd7f1f54 Ensure that ProtocolLib works with VanishNoPacket and Spigot 2013-02-05 16:06:49 +01:00
Kristian S. Stangeland 901ab1fdda Add support for the Netty Spigot build.
This required extensive reworking of the inner packet injection system
in ProtocolLib. 

I've also fixed a minior bug in the fuzzy member contract class.
2013-02-05 07:00:49 +01:00
Kristian S. Stangeland e8112dba0e Made IntegerSet accessible. Encapsulating the injection handler. 2013-02-05 01:37:41 +01:00
Kristian S. Stangeland 4685a06d27 Made the constructors for the different injectors more generic.
We now use builders. This should make it simpler to introduce Netty
Spigot support.
2013-02-04 22:42:07 +01:00
Kristian S. Stangeland 7abfd2148e Make it possible to specify a custom incoming packet interceptor. 2013-02-03 20:27:47 +01:00
Kristian S. Stangeland 6cf3307a3b Moved packet-related classes to a seperate package.
This is backwards compatible because they were all package private.
2013-02-03 19:24:39 +01:00
Kristian S. Stangeland 3219deed79 Use isMinecraftClass to check for NMS classes. 2013-02-03 19:07:12 +01:00
Kristian S. Stangeland eaf7ea9618 Add an equality and hashCode() method to all the matchers. 2013-02-03 04:47:02 +01:00
Kristian S. Stangeland 75f05732bb Improve the toString()-functionality of each fuzzy contract. 2013-02-03 04:08:30 +01:00
Kristian S. Stangeland 58969b26f6 Add getters and setters for each of the fuzzy contracts. 2013-02-03 03:13:10 +01:00
Kristian S. Stangeland d583f7ccc3 Moving fuzzy reflection "framework" to a seperate package. 2013-02-03 02:07:23 +01:00
Kristian S. Stangeland ab5bc1fd25 Make people aware of NULLs in the StreamSerializer class. 2013-02-01 01:43:40 +01:00
Kristian S. Stangeland c7caf16a94 Inform the server operator about problems with Guava. 2013-02-01 01:32:48 +01:00
Kristian S. Stangeland b26c9c45c8 Make it possible to send and recieve packets during login. 2013-01-31 22:52:33 +01:00
Kristian S. Stangeland ea08696d10 Fix a serious bug that caused Blocks to inadvertanly be created.
Our custom IOC system (DefaultInstances) would call new Block(0) on a
MCPC based CraftBukkit system, causing air blocks to be treated as 
solid blocks. We prevented this by explicitly forbidding it from 
creating instances of ItemStack or Block in our structure modifier's
default field generator.

A second bug was caused by mismatched methods in WrappedDataWatcher,
which we solved by simply trying to use them, and then swapping them 
if we encounter any errors or weird state.
2013-01-31 20:30:08 +01:00
Kristian S. Stangeland e8c615b203 Attempting to add support for MCPC.
Still have to track down a very elusive bug however.
2013-01-31 06:23:01 +01:00
Kristian S. Stangeland 8b12907dfb Improve JavaDoc and add the ability to match declaring class. 2013-01-30 18:44:13 +01:00
Kristian S. Stangeland 2f8912a8ae Improved the fuzzy reflection system tremendously.
It is now possible to specify exactly what method or field you're
looking for, based on a number of different critera such as return
value, parameter count or parameter type, exceptions, modifiers, name -
all using a very fluent builder syntax.
2013-01-30 05:08:38 +01:00
Kristian S. Stangeland 58017960c9 Don't reference classes by name - it is not safe. 2013-01-30 01:45:00 +01:00
Kristian S. Stangeland d109506cf2 Automatically delete empty JAR-files left over from upgrades. 2013-01-27 16:42:30 +01:00
Kristian S. Stangeland eb996e73d8 Fixed a bug causing the entity modifier to always lookup the same world
The entity modifier would try to cache each equivalent converter by its
specific type - i.e. whether or not it converts entities or watchable
collections - but this isn't enough to distinguish entity modifiers 
as they store a hidden reference to the world the entity is supposed 
to be occupying. 

Thus, after the first entity modifier is used for "world", it would 
be subsequently cached for every world ("world_nether", "world_end", 
etc.). The end result was that entity modifiers would only work for 
one world.

This patch fixes this problem by adding a proper equals() method to
the entity equivalent converter class, along with all the other 
equvialent converter classes, in case it should prove to become useful
in the future.
2013-01-27 15:35:39 +01:00
Kristian S. Stangeland 4f061e3464 Incremented to 2.1.1-SNAPSHOT for development towards the next version. 2013-01-22 13:56:05 +01:00
Kristian S. Stangeland 74ecb14a33 Fixed a couple of mistakes in the JavaDoc. 2013-01-22 13:33:24 +01:00
Kristian S. Stangeland 6ed5d4687d Updating JavaDoc for 2.1.0 2013-01-22 13:29:54 +01:00
Kristian S. Stangeland 290b791eb3 Merge branch 'master' into gh-pages 2013-01-22 13:26:58 +01:00
Kristian S. Stangeland c6d896d717 Bumping version to 2.1.0
This is because the previous release had a mistake in the API.
2013-01-22 12:59:08 +01:00
Kristian S. Stangeland 2a8dc4ad22 Don't consider the current file as a candidate for conflicting version. 2013-01-22 03:27:26 +01:00
Kristian S. Stangeland 14f667e2b9 The client no longer sends a RESPAWN packet. 2013-01-20 21:03:17 +01:00
Kristian S. Stangeland b138332608 Compile PL for 1.4.7 - though earlier versions will still work. 2013-01-18 15:36:42 +01:00
Kristian S. Stangeland 5c098339a9 Increase default waiting period to 2 seconds. 2013-01-18 09:56:55 +01:00
Kristian S. Stangeland 093a6f12bf Properly exit BlockingHashMap#get if we time out. 2013-01-18 09:46:23 +01:00
Kristian S. Stangeland 95438c1861 Throw an exception the caller can handle instead of a warning message.
This should prevent possible spam by plugins that are not aware that a 
player has either logged out, or is not injected due to a bug in
ProtocolLib.
2013-01-17 12:26:39 +01:00
Kristian S. Stangeland d23a45081a Better error messages - inform that updating is disabled for SNAPSHOTs. 2013-01-15 11:09:16 +01:00
Kristian S. Stangeland 1880add7dc Just report HTTP errors, no need to make tickets out of them. 2013-01-13 18:00:34 +01:00
Kristian S. Stangeland 429ca5d447 Don't update hooks if we don't need to. 2013-01-13 15:44:16 +01:00
Kristian S. Stangeland 651581d677 FIXED: DataWatcher can now clone ItemStacks and ChunkPositions.
Due to a oversight, we retrieved the "wrapped" value in the get cloned
helper method. This caused it to not clone the chunk position and item
stack.
2013-01-13 15:22:55 +01:00
Kristian S. Stangeland 637d6cda18 Only set the hook if it's different 2013-01-13 14:58:07 +01:00
Kristian S. Stangeland 70cb9c5275 Updated packet registry for 1.4.6. 2013-01-13 14:31:19 +01:00
Kristian S. Stangeland 37a9fc6a98 Don't call deprecated methods. 2013-01-13 11:41:32 +01:00
Kristian S. Stangeland f416d1ba3d Make the backwards compatible scheduler available to all. 2013-01-13 11:33:53 +01:00
Kristian S. Stangeland 845f339a30 Add the ability to set the default injection hook method.
This is useful for debugging.
2013-01-13 11:31:32 +01:00
Kristian S. Stangeland 17a5f59c0d Added a convenience method for boolean structure modifiers. 2013-01-11 00:07:15 +01:00
Kristian S. Stangeland 29709c4159 Fix a small oversight in the API design. 2013-01-10 01:26:45 +01:00
Kristian S. Stangeland 898eb405f0 Incremented to 2.0.1-SNAPSHOT for development towards the next version 2013-01-10 01:00:16 +01:00
Kristian S. Stangeland a6755d0ddd Updated JavaDoc for 2.0.0 2013-01-10 00:58:34 +01:00
Kristian S. Stangeland 9778ed6fc5 Merge branch 'master' into gh-pages 2013-01-10 00:52:35 +01:00
Kristian S. Stangeland 9c86a36afb Stop tracking the dependency reduced POM. 2013-01-10 00:51:52 +01:00
Kristian S. Stangeland 12c15b23ab Increment to 2.0.0 instead.
This is to indiciate that this version is incompatible with older 
versions. Although, it should really have occured at 1.8.0.
2013-01-10 00:21:51 +01:00
Kristian S. Stangeland f2c955985c Increment to version 1.10.0 2013-01-09 23:35:14 +01:00
Kristian S. Stangeland 10d5163ef5 Added the ability to save int arrays in configurations. 2013-01-09 14:45:00 +01:00
Kristian S. Stangeland 221ce71b97 Increment to 1.9.2-SNAPSHOT. 2013-01-09 07:06:57 +01:00
Kristian S. Stangeland 0597084c3d Switched to a better base64 encoding and decoding method. 2013-01-09 07:05:36 +01:00
Kristian S. Stangeland f791e2726f Inform about the purpose of the fromItemStack (fromItemTag) method. 2013-01-09 04:52:53 +01:00
Kristian S. Stangeland dbc28c0035 Added the ability to serialize and deserialize NBT to many formats. 2013-01-09 04:50:04 +01:00
Kristian S. Stangeland 3426306805 Finalize the operation to make the NBT wrappers into interfaces. 2013-01-08 22:24:07 +01:00
Kristian S. Stangeland c2ea92ab37 Refactoring: Made NbtList and NbtCompound into generic interfaces. 2013-01-08 21:11:29 +01:00
Kristian S. Stangeland 749f006621 Add the ability to serialize ItemStacks to strings. 2013-01-08 03:33:24 +01:00
Kristian S. Stangeland 4756de8066 Factory methods should return NBTWrapper. 2013-01-08 03:16:21 +01:00
Kristian S. Stangeland 79daec662c Added GPL license information. 2013-01-08 02:05:15 +01:00
Kristian S. Stangeland 9fb5f7f2dd Fine tuning of the NBT system. Added more test cases. 2013-01-08 02:00:42 +01:00
Kristian S. Stangeland 70589a6263 Added the ability to read NBT ItemStack tags. 2013-01-07 23:39:16 +01:00
Kristian S. Stangeland 38137cea2c Verify element type in tag list. 2013-01-07 20:37:21 +01:00
Kristian S. Stangeland 6b3d85af62 Change "clone" to "deepClone", in line with PacketContainer. 2013-01-07 20:24:15 +01:00
Kristian S. Stangeland 8abb93114f Ensure that we can create compounds and lists. 2013-01-07 20:15:21 +01:00
Kristian S. Stangeland 671654aaaf Add support for reading and writing NBT tags in packets.
This wraps the internal NBT read/write system in Minecraft.
2013-01-07 10:14:25 +01:00
Kristian S. Stangeland 73e71ff954 Don't load classes on secondary threads. ADDRESSES TICKET-27.
We'll move the "load class" call to the main thread, hopefully fixing
ticket 27. The secondary thread will still call the "define class"
protected method, which may not be thread safe.
2013-01-06 20:14:25 +01:00
Kristian S. Stangeland 07ae10bcd6 This is a snapshot version. 2013-01-02 20:39:10 +01:00
Kristian S. Stangeland 6996db9e63 Add a serializer for reading and writing ItemStacks to byte arrays.
To aid discoverability, we add a short-cut to this serializer in
PacketContainer, next to the getByteArrays() method.
2013-01-02 20:37:24 +01:00
Kristian S. Stangeland 3723e73e81 Reverted the attempt at automating the version number in plugin.yml. 2013-01-02 20:17:15 +01:00
Kristian S. Stangeland 711ffb1db7 Updated Metrics to version 6. 2013-01-02 20:16:53 +01:00
Kristian S. Stangeland f995accd0c Let maven automatically insert the correct version in plugin.yml 2012-12-27 19:00:49 +01:00
Kristian S. Stangeland 6bd8ec5791 Updated to 1.9.1-SNAPSHOT for development towards the next version. 2012-12-27 17:46:09 +01:00
Kristian S. Stangeland 599e3b8828 Updated JavaDoc for 1.9.0 2012-12-27 17:44:04 +01:00
Kristian S. Stangeland 044a58f657 Merge branch 'master' into gh-pages 2012-12-27 17:41:23 +01:00
Kristian S. Stangeland fd69f08a1d Maven crap. 2012-12-27 17:37:45 +01:00
Kristian S. Stangeland 82ec3dc5f1 Bumping to 1.9.0 to indicate that the cloning API is improved. 2012-12-27 17:03:30 +01:00
Kristian S. Stangeland 0d34b82d72 Don't copy inflated buffers - they won't be modified in any case.
Resulted in some pretty ugly factory code.
2012-12-27 16:28:30 +01:00
Kristian S. Stangeland 20ab2a71ac Assume SecretKey and PublicKey is immutable too. 2012-12-27 15:11:08 +01:00
Kristian S. Stangeland d16e0bf1c6 Add support for cloning maps. 2012-12-27 15:06:25 +01:00
Kristian S. Stangeland f0e7b9aec7 Added missing copyright notices (GPL). 2012-12-27 14:28:33 +01:00
Kristian S. Stangeland 01b9e15930 No need to expose the MinecraftVersion class. 2012-12-27 14:23:22 +01:00
Kristian S. Stangeland 0f30a11510 Bumping version to 1.8.5-SNAPSHOT. 2012-12-27 12:12:44 +01:00
Kristian S. Stangeland 447f01a3dd Crash if multiple copies of ProtocolLib are running simultaneously. 2012-12-27 12:12:11 +01:00
Kristian S. Stangeland 695a4f3b0e Added a shallow clone method.
Removed the identity cloner, as it's no longer needed.
2012-12-27 11:13:42 +01:00
Kristian S. Stangeland dea725e1e3 Move the field cloning reponsibility to FieldCloner. 2012-12-27 10:32:26 +01:00
Kristian S. Stangeland 8b91e3034d Added a generic cloning library to properly support deepClone(). 2012-12-27 09:23:07 +01:00
Kristian S. Stangeland fdbd3d6495 Rename ObjectCloner to ObjectWriter.
It's more accurately thought off as a way of writing the content of
object A to object B, than a generic cloning method.
2012-12-25 15:46:33 +01:00
Kristian S. Stangeland 655fbf4799 Added StructureModifier tests for different packet types. 2012-12-25 15:44:14 +01:00
Kristian S. Stangeland 425c5fd682 Add equals() and hashcode() to the wrapped DataWatcher. 2012-12-24 21:42:48 +01:00
Kristian S. Stangeland cc3441dfeb Continue to remove unnecessary raw types. 2012-12-23 18:52:00 +01:00
Kristian S. Stangeland 450fbd8e8d Bumping to beta 1.4.6 of CraftBukkit 2012-12-23 04:22:02 +01:00
Kristian S. Stangeland d328111b38 No need to suppress any warnings - we can use wildcards. 2012-12-22 23:16:21 +01:00
Kristian S. Stangeland 5d06b9e84c Bumping to version 1.8.4-SNAPSHOT. 2012-12-21 18:33:40 +01:00
Kristian S. Stangeland 555b5d0795 Create a typed ItemStack array when writing it to a field. FIXES 24.
After the package versioning commit, ProtocolLib switched to performing
everything exclusively with reflection. Unfortunately, this may have
introduced a couple of new bugs.

This problem is caused by the fact that ProtocolLib converts a Bukkit
ItemStack array to an Object array instead of an array of NMS 
ItemStacks. This object array can't be assigned to a ItemStack array,
so the write operation fails.

The fix is to create a typed array with reflection.
2012-12-21 18:33:09 +01:00
Kristian S. Stangeland 35083f87fe Increment to version 1.8.3-SNAPSHOT. 2012-12-21 00:37:46 +01:00
Kristian S. Stangeland c71bf61511 1.4.6 appears to be working properly. 2012-12-21 00:36:04 +01:00
Kristian S. Stangeland 5a96a63ff8 Added support for 1.4.6.
Yes, it's essentially a hack, but I don't have enough time today to
find a better way. It will have to do until 1.5.0 is out.
2012-12-21 00:27:13 +01:00
Kristian S. Stangeland 220c0e4bc5 Ensure that CraftItemStacks can be converted to NMS item stacks. 2012-12-19 23:48:31 +01:00
Kristian S. Stangeland 3b142db569 Don't ignore critical exceptions - print them. 2012-12-19 23:10:50 +01:00
Kristian S. Stangeland 1509f5cd8b Increasing to 1.8.2-SNAPSHOT for development toward the next version. 2012-12-19 23:10:28 +01:00
Kristian S. Stangeland 3b0790f136 Bumping to release of 1.8.1 2012-12-19 18:04:44 +01:00
Kristian S. Stangeland b0cc1440dd Revert "Remove unused classes from CGLib. Saves us 136 kB. "
This reverts commit b1bd7e9fcd.
2012-12-19 17:52:56 +01:00
Kristian S. Stangeland d7d6e24506 Added support for the new way of converting CraftItemStacks in 1.4.5 2012-12-19 17:51:21 +01:00
Kristian S. Stangeland b1bd7e9fcd Remove unused classes from CGLib. Saves us 136 kB. 2012-12-15 07:17:04 +01:00
Kristian S. Stangeland 79afb0bcdc Forgot to add the target object in the ChunkPosition wrapper. 2012-12-13 00:39:50 +01:00
Kristian S. Stangeland 86f15c8674 Fallback on our own custom object printer. 2012-12-12 18:35:58 +01:00
Kristian S. Stangeland 4440e6fcaa Prevent ProtocolLib from spamming the console too. 2012-12-12 18:34:02 +01:00
Kristian S. Stangeland 15961f6cce We can include the stack trace - it will only very rarely be printed. 2012-12-12 08:41:55 +01:00
Kristian S. Stangeland 7117545509 Prevent plugins from spamming the console. 2012-12-12 08:32:37 +01:00
Kristian S. Stangeland e855be64ad Default settings matter. Make auto-update opt-in. 2012-12-12 07:41:58 +01:00
Kristian S. Stangeland 781a945ef4 Bumping to 1.8.1-SNAPSHOT for development towards the next version. 2012-12-12 07:41:19 +01:00
Kristian S. Stangeland 4d0aa5e1c1 Updating documentation for 1.8.0 2012-12-10 16:45:24 +01:00
Kristian S. Stangeland 3d166ae353 Merge branch 'master' into gh-pages 2012-12-10 16:43:44 +01:00
Kristian S. Stangeland afdb0d0324 Remember to perform deep cloning. 2012-12-10 16:30:48 +01:00
Kristian S. Stangeland 40345f25ff Bumping to version 1.8.0 2012-12-10 16:10:56 +01:00
Kristian S. Stangeland f6f8c9f80f Only broadcast a notifcation message in the event of a warning. 2012-12-10 05:50:18 +01:00
Kristian S. Stangeland b135bf86c3 Add the ability to skip the version check for a specific MC version. 2012-12-09 17:09:24 +01:00
Kristian S. Stangeland 7845a844d4 Make ProtocolLib load on startup. No need to wait for the world to load 2012-12-09 15:35:01 +01:00
Kristian S. Stangeland 83f5a7e5b7 Add packet information to the default packet error reporter. 2012-12-09 04:51:11 +01:00
Kristian S. Stangeland 7ca3e916a5 Add a warning message when ProtocolLib is started on untested version.
May decide to simply stop ProtocolLib unless this feature is
specifically disabled.
2012-12-09 04:23:26 +01:00
Kristian S. Stangeland 6b5ecd7309 Improve the functionality of wrapped DataWatcher and WatchableObject. 2012-12-08 14:22:07 +01:00
Kristian S. Stangeland af546e7f1d Ignore depcreciation - we're using the method correctly. 2012-12-06 04:04:07 +01:00
Kristian S. Stangeland 6530db23b7 Last commit contains breaking API changes, so increment version. 2012-12-06 03:54:25 +01:00
Kristian S. Stangeland 0745fba881 Massive update that ensures ProtocolLib is compatible with 1.4.5-R3.0
Due to 8f12382e8efc8c39a919af9180dd884caf3720ff, CraftBukkit has
now moved the net.minecraft.server package and org.bukkit.craftbukkit
to versioned package names, breaking all plugins that rely on these
directly.

ProtocolLib was never intended to be updated specifcally for new
versions of Minecraft (hence the index-based system of accessing
packets instead of making wrapper classes), so we will use reflection
to "ignore" this hack entirely. Luckily, ProtocolLib was initially 
designed for this in mind, so we don't have to refactor it entirely.
2012-12-06 03:37:20 +01:00
Kristian S. Stangeland 45c28d47c8 Create a new dedicated Minecraft reflection class. 2012-12-05 23:47:55 +01:00
Kristian S. Stangeland 88009292bb Update to the new CraftBukkit snapshot. 2012-12-05 23:47:36 +01:00
Kristian S. Stangeland 25d41ed51c Ensure that fake cancellation packets can be created. 2012-12-05 03:32:13 +01:00
Kristian S. Stangeland ede60970b9 Ensure that we don't cache wrong methods in the serialization. 2012-12-04 16:21:37 +01:00
Kristian S. Stangeland 86d3461700 Ensure that we don't return enhancer classes as vanilla classes. 2012-12-04 14:59:08 +01:00
Kristian S. Stangeland 733ecdd59b Increment to 1.7.2-SNAPSHOT for development towards the next version. 2012-12-04 14:49:59 +01:00
Kristian S. Stangeland e0455b76b8 Updated to 1.7.1 - removed test classes. 2012-12-04 01:02:05 +01:00
Kristian S. Stangeland cc88232c8e Updated documentation for 1.7.1 2012-12-04 00:58:16 +01:00
Kristian S. Stangeland 0129a0575c Merge branch 'master' into gh-pages 2012-12-04 00:54:36 +01:00
Kristian S. Stangeland 98edcb1772 Maven crap again. 2012-12-04 00:54:25 +01:00
Kristian S. Stangeland 7062b9327c Fix documentation. 2012-12-04 00:21:14 +01:00
Kristian S. Stangeland b9abf0b683 Incrementing to 1.7.1 2012-12-04 00:19:55 +01:00
Kristian S. Stangeland 98a9377384 Add a convenience retriever for integer array fields. 2012-12-04 00:18:33 +01:00
Kristian S. Stangeland 300111b850 Make it easy to clone packets. 2012-12-03 17:51:18 +01:00
Kristian S. Stangeland f4accbfe2d Make it possible to get every player that is tracking a given entity. 2012-12-02 02:49:11 +01:00
Kristian S. Stangeland 2bd06922e0 Ensure that the wrapped watchable object returns wrapped objects.
In addition, made it possible to construct watchable objects directly.
Also fixed a bug preventing data watchers from creating new watchable
objects.
2012-12-01 22:34:46 +01:00
Kristian S. Stangeland f81e3262d0 Don't spam the console when a player logs out quickly. 2012-12-01 17:24:46 +01:00
Kristian S. Stangeland beb4bba7fa Switching away from checked exceptions. 2012-11-27 08:00:52 +01:00
Kristian S. Stangeland 03ad9be078 Be careful when registering commands. It shouldn't break the plugin.
Found an error report in the wild suggesting that getCommand() might
occationally fail (if plugin.yml isn't loaded properly, maybe) and
return NULL instead.

Since the commands are only for administrators and plugin authors, 
we'll simply ignore it if this occurs.
2012-11-23 11:42:17 +01:00
Kristian S. Stangeland d53b6dbee1 Removed the ReadWriteLock as it's superseeded by the BlockingHashMap. 2012-11-23 08:52:02 +01:00
Kristian S. Stangeland c49b1ddc02 Incremented to 1.7.1-SNAPSHOT for development to the next version. 2012-11-23 08:48:32 +01:00
Kristian S. Stangeland f5cb7ddc7b Adding a blocking hash map to use while looking up data connections. 2012-11-23 08:48:08 +01:00
Kristian S. Stangeland a2b61ea880 Updated documentation for 1.7.0 2012-11-21 06:29:29 +01:00
Kristian S. Stangeland 1d1f96cda4 Merge branch 'master' into gh-pages 2012-11-21 06:27:32 +01:00
Kristian S. Stangeland a4f79ccb3f Maven crap. 2012-11-21 06:27:18 +01:00
Kristian S. Stangeland 78387a033b Bumping to version 1.7.0 2012-11-21 05:57:07 +01:00
Kristian S. Stangeland 21a1dfcbac Preserve intervals outside the given range to remove. 2012-11-21 05:53:26 +01:00
Kristian S. Stangeland 4b2f69c3c8 Fix removing intervals in the interval tree. 2012-11-21 05:50:23 +01:00
Kristian S. Stangeland 4b19f8498b Allow timeout listeners to cancel expiration. 2012-11-21 03:40:31 +01:00
Kristian S. Stangeland 95fe40aef3 Discurage sending of cancelled packets in ProtocolManager too. 2012-11-21 02:48:14 +01:00
Kristian S. Stangeland 8c4b4fcaa4 Advertise the incrementProcessingDelay() function.
Discurage plugins of re-sending cancelled packets, as it makes it 
impossible for other plugins to take part in the processing.

Assume plugin A delays transmission of packet X by cancelling the event,
and then retransmitting X outside the filters. It is then impossible
for another plugin B to extend the delay without fighting plugin A
for control over the packet, for instance by decreasing the listener
priority and cancelling first.

It is much better for plugin A to call incrementProcessingDelay() in
an asynchronous listener. Then plugin B can do the same, and the
packet will be sent after both plugins has called
signalProcessingDone().
2012-11-21 02:42:18 +01:00
Kristian S. Stangeland 524ef2e6c9 Fix the packet sending procedure. 2012-11-21 02:18:56 +01:00
Kristian S. Stangeland 858f9ee02d Renamed 'synchronizeMain' to 'notThreadSafe'. 2012-11-21 01:42:59 +01:00
Kristian S. Stangeland 0b200472f1 Process asynchronous packets on an async thread.
Bukkit complains if we try to send an async packet on the main thread,
so we will have to add a new background thread that can transmit
packets processed by light-weight packet listeners.

In addition, fixed a bug causing the "uncancel" method in 
PacketInjector from not working properly. That bug is as persistent
as a zombie.
2012-11-21 01:39:43 +01:00
Kristian S. Stangeland 9482818751 Fixed an incorrect null check. 2012-11-21 00:24:19 +01:00
Kristian S. Stangeland 36f867cafa Added synchronous packet processing.
Client packets are typically processed asynchronously (in a client's
reader thread), and should never access the Bukkit API directly,
with a few exceptions. This is problematic if you need to cancel a
packet as a response to the Bukkit API, such as the permission system.

Currently, you will have to either cancel the packet - which is 
discuraged - sync with the main thread and then re-transmit it outside
the filters, or use an asynchronous thread with callSyncMethod and 
wait on the returned future. A better method is needed.

Synchronous processing allows you to run light-weight packet listeners
on the main thread without having to deal with synchronization, 
concurrency or the overhead of an additional thread. It can also
process multiple packets per tick with a configurable timeout.

This, along with 7b9d971238, makes it
easy to delay light-weight packets to be synchronously processed.
2012-11-21 00:15:53 +01:00
Kristian S. Stangeland dd9cb30d25 Structure compiler shouldn't be public. 2012-11-20 18:47:22 +01:00
Kristian S. Stangeland 53fe3e5b61 Fixed a range of smaller bugs discovered by FindBugs. 2012-11-20 07:10:46 +01:00
Kristian S. Stangeland a849c38ce6 Implement equals() and hashCode(), since we've implemented compareTo. 2012-11-20 06:59:56 +01:00
Kristian S. Stangeland d4d763af94 Add a warning message when trying to correct invalid configuration. 2012-11-20 06:53:39 +01:00
Kristian S. Stangeland dc186df695 Small fix for OpenJDK (FindBugs). 2012-11-20 06:48:05 +01:00
Kristian S. Stangeland 95dbddf9bb Fixed a number of minor bugs. 2012-11-20 06:46:21 +01:00
Kristian S. Stangeland 023c3908ae Fixed error reporter not being assigned properly. 2012-11-20 06:33:54 +01:00
Kristian S. Stangeland ac993896cc Fixed writing private fields with a compiled structure modifier.
Incredibly hard to track down. Lucked out by randomly removing a
semicolon.
2012-11-20 06:17:52 +01:00
Kristian S. Stangeland 456764468a Always generate classes with legal identifiers.
Certain types, such as ItemStack[], would cause the StructureCompiler
to generate classes with the name CompiledStructure@ParentItemStack[],
which are not legal names.

Instead, we'll replace the brackets with the word Array.

In addition, to accomodate classes with identical names, we'll use
the following naming convention instead:
CompiledStructure$[Canonical name of target]$Canonical name of type],
where the canonical name (net.minecraft.server.ItemStack[]) is 
transformed to a legal name by replacing "." to "_" and "[]" to array.

In our example, that would result in the following class name:
    net_minecraft_server_ItemStackArray
2012-11-20 03:50:44 +01:00
Kristian S. Stangeland 9170e48992 Correct the concurrent initialization pattern. 2012-11-20 03:32:44 +01:00
Kristian S. Stangeland 7b9d971238 Use a separate asynchronous sending queue for every online player.
This ensures that packets intended for player A doesn't have to wait
for the packets of player B to be finished processing.
2012-11-20 03:23:43 +01:00
Kristian S. Stangeland f8af92eb5b Add the error reporter to the background compiler. 2012-11-20 00:26:16 +01:00
Kristian S. Stangeland d5aa1cde51 Added the ability to enable or disable the background compiler. 2012-11-19 23:44:13 +01:00
Kristian S. Stangeland cdc5740f85 Log the plugin version too. 2012-11-19 23:27:04 +01:00
Kristian S. Stangeland c2209138fe Add the ability to clone a watchable object. 2012-11-19 20:43:13 +01:00
Kristian S. Stangeland 45f5d55b6a Fixed the update feature (hopefully for the last time).
The problem is that ProtocolLib is using a different naming convention 
where each release has a version suffix. So, while we can't use the
update folder to replace the JAR file, it's also not needed since
we can simply add it to the plugins directory directly and remove
the old version on shutdown.
2012-11-17 08:47:25 +01:00
Kristian S. Stangeland 4298ac609d Damn typos. 2012-11-16 04:10:59 +01:00
Kristian S. Stangeland 15ca240bac Fixed a bug that would clear the debug listeners on "add".
This was caused by a bug in the abstract interval tree class that would
clear the entire tree instead of the subtree whenever a new entry was
added.

Never roll your own custom collection implementation ...
2012-11-16 03:57:55 +01:00
Kristian S. Stangeland a40df621d0 Spelling error. 2012-11-13 18:31:06 +01:00
Kristian S. Stangeland 2a86a4cbc5 Updated documentation to 1.6.0 2012-11-13 17:54:42 +01:00
Kristian S. Stangeland b6478924bb Merge branch 'master' into gh-pages 2012-11-13 17:50:54 +01:00
Kristian S. Stangeland b84161fde2 Fix documentation. 2012-11-13 17:50:36 +01:00
Kristian S. Stangeland e28c2331e2 Incrementing to 1.6.1-SNAPSHOT for development to the next version. 2012-11-13 17:50:22 +01:00
Kristian S. Stangeland 5c2e692a90 Merge branch 'master' into gh-pages 2012-11-13 17:46:06 +01:00
Kristian S. Stangeland c2d79c142f Generated changes. 2012-11-13 17:45:49 +01:00
Kristian S. Stangeland 189497fffa Bumping version to 1.6.0 2012-11-13 17:08:50 +01:00
Kristian S. Stangeland 235e6eeed2 Made it possible to convert between a chunk position and a coordinate. 2012-11-13 16:49:49 +01:00
Kristian S. Stangeland 80b99fccd9 Added some array methods too. 2012-11-13 16:43:52 +01:00
Kristian S. Stangeland 7f5288dea6 Add getIntegers(), getLongs(), ect. to PacketContainer. 2012-11-13 16:23:19 +01:00
Kristian S. Stangeland 528468a342 Fixed a bug preventing the entity modifier from reading entities. 2012-11-13 16:10:56 +01:00
Kristian S. Stangeland fec2734fe2 Added a couple of useful methods to the wrappers. 2012-11-13 15:11:54 +01:00
Kristian S. Stangeland ad69b0caac Added the ability to read data watchers and watchable object lists. 2012-11-13 14:34:07 +01:00
Kristian S. Stangeland a567721114 Add the ability to read and write ChunkPositions. 2012-11-11 07:24:45 +01:00
Kristian S. Stangeland 5bb6f7649a Corrected a bug in a PacketAdapter constructor discovered by Folipurba. 2012-11-11 06:04:08 +01:00
Kristian S. Stangeland 0b292af3b1 Add the ability to write to final fields, even if it is compiled. 2012-11-11 02:09:45 +01:00
Kristian S. Stangeland 76d27017de Allow the structure modifier to read final fields. 2012-11-11 01:22:17 +01:00
Kristian S. Stangeland addc62457a Handle multiple connection sides properly. 2012-11-11 01:06:11 +01:00
Kristian S. Stangeland 79786b8192 Use a default connection side in the packet command. 2012-11-11 00:57:16 +01:00
Kristian S. Stangeland da7cd0b10d Reference the correct permissions. 2012-11-10 07:58:44 +01:00
Kristian S. Stangeland 003bc927a4 Set default update rate to 43200 seconds instead. 2012-11-06 18:07:06 +01:00
Kristian S. Stangeland 5e6a6f6a95 Fix the syntax of the packet debug message. 2012-11-05 00:05:16 +01:00
Kristian S. Stangeland 0108c3390e Add exception handlers to all the commands. 2012-11-04 16:14:07 +01:00
Kristian S. Stangeland 5b279660e0 Increment to 1.5.2-SNAPSHOT for development on the next version. 2012-11-04 16:13:47 +01:00
Kristian S. Stangeland 77c707923f Updating JavaDoc for 1.5.1 2012-11-04 03:50:58 +01:00
Kristian S. Stangeland 23fb21d234 Merge branch 'master' into gh-pages
Conflicts:
	ProtocolLib/.classpath
2012-11-04 03:46:33 +01:00
Kristian S. Stangeland 2a5d240df9 Update POM. 2012-11-04 03:39:38 +01:00
Kristian S. Stangeland a5dbbc6170 Release of 1.5.1 2012-11-04 03:38:00 +01:00
Kristian S. Stangeland abafb7a6c5 Fix configuration handling. 2012-11-04 03:03:38 +01:00
Kristian S. Stangeland f5dce019df Save the last update check, even if it was initiated from a command. 2012-11-04 02:30:47 +01:00
Kristian S. Stangeland 64e3ba7f14 Add the ability to disable metrics in the config file. 2012-11-04 02:15:16 +01:00
Kristian S. Stangeland 2f2eb148fa Added a timeout listener. 2012-11-04 01:10:05 +01:00
Kristian S. Stangeland 6c8bda24fd Parse "detailed" as TRUE. 2012-11-04 00:06:44 +01:00
Kristian S. Stangeland a9fe6afa01 Divide names output into pages instead. 2012-11-03 09:27:20 +01:00
Kristian S. Stangeland 626dea07fa Move range parser out of CommandPacket. Make a common command base. 2012-11-03 08:51:03 +01:00
Kristian S. Stangeland 188f03b39e Fixed the columns slightly. Still needs tweaking. 2012-11-03 07:32:14 +01:00
Kristian S. Stangeland da0d55fcea Improve the names command. 2012-11-03 07:16:20 +01:00
Kristian S. Stangeland f2e078ce6a Print strings with quotation marks. 2012-11-03 06:59:47 +01:00
Kristian S. Stangeland 72bfa3da9b Adding the names sub-command. 2012-11-03 06:54:31 +01:00
Kristian S. Stangeland 27104c7350 Accept zero as a packet ID in the packet command. 2012-11-03 06:51:37 +01:00
Kristian S. Stangeland 263d085590 Always create vanilla packets in the packet constructor. 2012-11-03 06:48:58 +01:00
Kristian S. Stangeland f16581cdf4 Massive update. 2012-11-03 06:41:29 +01:00
Kristian S. Stangeland 413eb283a4 Fix handling of configuration. 2012-11-03 00:54:03 +01:00
Kristian S. Stangeland 7952da99c9 Added configuration and commands.
The two commands added are as follows:
 protocol:
  Reload the configuration, check for updates and download the 
  most recent version.
 packet:
  Add or remove a simple packet inspector. Can even display the 
  content of each packet.
2012-11-03 00:38:57 +01:00
Kristian S. Stangeland 3d0dce7e8d Make the update class easier to work with. 2012-11-02 18:55:23 +01:00
Kristian S. Stangeland 4328072f49 Created a default maven resources folder. 2012-11-02 17:47:33 +01:00
Kristian S. Stangeland 22aab024c2 Add a couple of permissions and commands. 2012-11-02 17:35:41 +01:00
Kristian S. Stangeland 18ec9bc973 Adding h31ix's auto update system. 2012-11-02 17:22:42 +01:00
Kristian S. Stangeland 2c00b570aa Increase default packet timeout to 30 minutes. 2012-11-02 16:58:56 +01:00
Kristian S. Stangeland 5ac15f11dc Drop expired packets in the processing chain. 2012-11-02 01:18:38 +01:00
Kristian S. Stangeland cda235af7e Increment the default async timeout to 2 minutes. 2012-11-02 01:03:12 +01:00
Kristian S. Stangeland 51184dd209 Ensure getEntityModifier is thread safe. FIXES Ticket-6.
Don't use the world's getPlayers-method, as it's enumerating a
collection that's not thread safe. This is important as
getEntityModifier may be called by a client packet listener (which is
async).
2012-11-02 00:35:35 +01:00
Kristian S. Stangeland aa9616d6b0 Don't check for closed sockets. If it talks like a duck, ect. 2012-10-31 00:52:02 +01:00
Kristian S. Stangeland ae08abe821 Remove PrimitiveUtils - it's already present in Bukkit's Guava library. 2012-10-29 22:15:25 +01:00
Kristian S. Stangeland d54cc35445 Updated to 1.5.1-SNAPSHOT for development of the next version. 2012-10-29 22:05:55 +01:00
Kristian S. Stangeland 0dc2bfef0c Handle exceptions in injected code.
This is very important, otherwise the server may crash unnecessarily.
2012-10-29 17:20:04 +01:00
Kristian S. Stangeland f9c0f212c1 Put UnsupportedListener at the package level. 2012-10-29 16:57:07 +01:00
Kristian S. Stangeland cbf4def71b Prevented map chunk listeners from crashing the server.
If the NetServerHandler injector fails and we revert to the network
field injector instead, any map chunk listener will crash the server
due to complications with Bukkit's ChunkCompressionThread. 

We avoid this by disabling map chunk listening entirely.
2012-10-29 16:54:53 +01:00
Kristian S. Stangeland 8636215b98 Add a couple of extra error handlers. 2012-10-29 04:21:12 +01:00
Kristian S. Stangeland 2239c1ea32 Add a better error reporter. 2012-10-29 04:17:53 +01:00
Kristian S. Stangeland 5e036185b8 Removed DEBUG message. 2012-10-29 02:39:05 +01:00
Kristian S. Stangeland 03da0e1974 Increment to 1.5.0 Release 2012-10-29 02:37:10 +01:00
Kristian S. Stangeland dfba924561 Clean up every static field, preempting potential ClassLoader leaks.
Note that this method is using some fairly ugly hacks and must be 
maintained/updated manually whenever a new class is added or removed.
2012-10-29 02:25:29 +01:00
Kristian S. Stangeland bdc41221db Fixed a serious misuse causing the creation of too many new classes.
The different injectors using CGLib was using a custom CallbackFilter
to optimize the resulting generated class. Unfortunately, the custom
filter didn't properly implement equals() and hashCode(), and so every
time a player logged, a new injector class had to be generated. 

This was fixed by making the injectors share a single CallbackFilter.

In addition, I've begun adding cleanup code that will reset all static
fields once the plugin has unloaded.
2012-10-29 01:42:22 +01:00
Kristian S. Stangeland eaf0b73c00 Increment version to 1.4.4-SNAPSHOT (skipping release of 1.4.3) 2012-10-28 06:10:31 +01:00
Kristian S. Stangeland cb2c7b4e0a Use a class cache when constructing the packet interceptor. 2012-10-28 06:09:29 +01:00
Kristian S. Stangeland 4e9b5009c8 Don't print messages to the console without a plugin prefix.
It's bad practice.
2012-10-23 00:54:41 +02:00
Kristian S. Stangeland 7536815e58 Set as SNAPSHOT in the plugin-file as well. 2012-10-23 00:41:53 +02:00
Kristian S. Stangeland 96ad954cf7 Prevent the PlayerQuitEvent from being fired twice. FIXES Ticket-2. 2012-10-23 00:37:35 +02:00
Kristian S. Stangeland 0aac8ebf0a Increment to 1.4.3 snapshot. 2012-10-21 22:40:26 +02:00
Kristian S. Stangeland a60dc3d778 Make the client packet reader thread-safe. 2012-10-21 22:39:56 +02:00
Kristian S. Stangeland 01c481dc93 Release of 1.4.2 2012-10-21 20:42:29 +02:00
Kristian S. Stangeland a51ad1f50f Properly remove previous hook, even if socket is NULL. FIXES Ticket-1. 2012-10-21 20:31:20 +02:00
Kristian S. Stangeland c08106a923 Increment to 1.4.2 snapshot. 2012-10-21 16:33:52 +02:00
Kristian S. Stangeland 63c468eff0 Packet constructor can now handle primitive parameter types. 2012-10-21 16:33:41 +02:00
Kristian S. Stangeland 43c0b5d8f1 Don't save NULL injectors in a ConcurrentHashMap. 2012-10-21 12:11:59 +02:00
Kristian S. Stangeland 331fc94190 Sending or receiving packets now work without listeners. 2012-10-19 16:27:49 +02:00
Kristian S. Stangeland caed0dcb11 Removed the spammy "Entity does not exist" error. 2012-10-19 00:54:03 +02:00
Kristian S. Stangeland 90f2caa5e8 Incremented to 1.4.1 2012-10-17 09:54:28 +02:00
Kristian S. Stangeland 69a5675161 Fixed a bug causing a StackOverflowException on packet construction. 2012-10-17 09:53:59 +02:00
Kristian S. Stangeland f8a8a645eb Added JavaDoc again. What the ... feels like everything is falling
apart.
2012-10-17 08:16:18 +02:00
Kristian S. Stangeland 32ed35d409 Adding GamePhase too. 2012-10-17 08:11:11 +02:00
Kristian S. Stangeland 242ddd5f40 Updated JavaDoc for 1.4.0 2012-10-17 08:07:20 +02:00
Kristian S. Stangeland b9ed36641f Merge branch 'master' into gh-pages
Conflicts:
	ProtocolLib/.classpath
	ProtocolLib/.settings/org.eclipse.core.resources.prefs
2012-10-17 08:01:30 +02:00
Kristian S. Stangeland 0622322ef4 Merged. 2012-10-17 07:58:24 +02:00
Kristian S. Stangeland 3f19105ce3 Updating JavaDoc for 1.3.1 2012-10-11 03:00:53 +02:00
Kristian S. Stangeland 37a77d2295 Merge branch 'master' into gh-pages 2012-10-11 02:57:57 +02:00
Kristian S. Stangeland 352e8d70c4 Adding JavaDoc for 1.3.0 2012-10-05 04:45:17 +02:00
Kristian S. Stangeland b54681d7c2 Merge branch 'master' into gh-pages 2012-10-05 04:43:53 +02:00
Kristian S. Stangeland f56cf3290e Merge branch 'master' into gh-pages 2012-10-05 04:40:27 +02:00
Kristian S. Stangeland 6a5794d0c0 Adding JavaDoc for 1.2.0 again (forgot to update index) 2012-10-01 05:05:47 +02:00
Kristian S. Stangeland 0a67cf3cd1 JavaDoc for 1.2.0 2012-10-01 05:01:08 +02:00
Kristian S. Stangeland 22cb77d78e Merge branch 'master' into gh-pages 2012-10-01 04:59:42 +02:00
Kristian S. Stangeland 10e5508c8a Updating JavaDoc to include sync/async information. 2012-09-19 03:49:49 +02:00
Kristian S. Stangeland f232823820 Merge branch 'master' into gh-pages 2012-09-19 03:49:04 +02:00
Kristian S. Stangeland a76e5835fd Regenerated JavaDoc. 2012-09-18 18:41:24 +02:00
Kristian S. Stangeland cdcb7ce656 Merge branch 'master' into gh-pages 2012-09-18 18:40:48 +02:00
Kristian S. Stangeland 99b9dc90ae Adding the new javadoc. 2012-09-18 17:27:59 +02:00
359 changed files with 60052 additions and 4354 deletions

No files matched your search

+174
View File
@@ -0,0 +1,174 @@
#################
## Eclipse
#################
# Generic project files
.project
.classpath
.settings/
bin/
*.pydevproject
.metadata
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.loadpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
#################
## Maven
#################
target/
dependency-reduced-pom.xml
#################
## Visual Studio
#################
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
.builds
*.dotCover
## TODO: If you have NuGet Package Restore enabled, uncomment this
#packages/
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
# ReSharper is a .NET coding add-in
_ReSharper*
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Others
[Bb]in
[Oo]bj
sql
TestResults
*.Cache
ClientBin
stylecop.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
############
## Windows
############
# Windows image file caches
Thumbs.db
# Folder config file
Desktop.ini
#############
## Python
#############
*.py[co]
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
# Mac crap
.DS_Store
# Log4J files
logs/
+13
View File
@@ -0,0 +1,13 @@
language: java
jdk:
- oraclejdk8
- oraclejdk7
script: mvn clean test -U
before_install: cd ProtocolLib
install: true
notifications:
email:
recipients:
- "dmulloy2@live.com"
on_success: change
on_failure: always
-27
View File
@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/ProtocolLib"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
-1
View File
@@ -1 +0,0 @@
target/
-23
View File
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ItemDisguise</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -1,12 +0,0 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.6
@@ -1,4 +0,0 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
-27
View File
@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
+167 -1
View File
@@ -1 +1,167 @@
target/
#################
## Eclipse
#################
*.pydevproject
.project
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
target/
.gitignore
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
#################
## Visual Studio
#################
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
.builds
*.dotCover
## TODO: If you have NuGet Package Restore enabled, uncomment this
#packages/
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
# ReSharper is a .NET coding add-in
_ReSharper*
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Others
[Bb]in
[Oo]bj
sql
TestResults
*.Cache
ClientBin
stylecop.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
############
## Windows
############
# Windows image file caches
Thumbs.db
# Folder config file
Desktop.ini
#############
## Python
#############
*.py[co]
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg
# Mac crap
.DS_Store
/target
/target
-23
View File
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ProtocolLib</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -1,4 +0,0 @@
eclipse.preferences.version=1
encoding//src/main/java=cp1252
encoding//src/test/java=cp1252
encoding/<project>=cp1252
@@ -1,12 +0,0 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.6
@@ -1,4 +0,0 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
-187
View File
@@ -1,187 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name>
<version>1.4.0</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
<developers>
<developer>
<id>aadnk</id>
<name>Kristian S. Stangeland</name>
<email>kr_stang@hotmail.com</email>
<url>http://comphenix.net/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
</roles>
<timezone>1</timezone>
</developer>
</developers>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:git://github.com/aadnk/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:aadnk/ProtocolLib.git</developerConnection>
<url>https://github.com/aadnk/ProtocolLib</url>
</scm>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<defaultGoal>clean install</defaultGoal>
<resources>
<resource>
<directory>src/main/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>true</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.sf</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.bukkit:craftbukkit</exclude>
<exclude>junit:junit</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release-sign-artifacts</id>
<build>
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>bukkit-rep</id>
<url>http://repo.bukkit.org/content/groups/public</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.3.2-R1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<threshold>High</threshold>
<effort>Default</effort>
</configuration>
</plugin>
</plugins>
</reporting>
<distributionManagement>
<repository>
<id>comphenix-releases</id>
<name>Comphenix Maven Releases</name>
<url>http://repo.comphenix.net/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>comphenix-snapshots</id>
<name>Comphenix Maven Snapshots</name>
<url>http://repo.comphenix.net/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<properties>
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
</properties>
</project>
+213 -128
View File
@@ -1,53 +1,45 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>1.4.0</version>
<packaging>jar</packaging>
<name>ProtocolLib</name>
<version>3.6.5</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>http://www.spigotmc.org/resources/protocollib.1997/</url>
<packaging>jar</packaging>
<parent>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib-Parent</artifactId>
<version>v3-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<properties>
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.number></project.build.number>
</properties>
<distributionManagement>
<repository>
<id>comphenix-releases</id>
<name>Comphenix Maven Releases</name>
<url>http://repo.comphenix.net/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>comphenix-snapshots</id>
<name>Comphenix Maven Snapshots</name>
<url>http://repo.comphenix.net/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<repositories>
<repository>
<id>bukkit-rep</id>
<url>http://repo.bukkit.org/content/groups/public</url>
</repository>
</repositories>
<build>
<defaultGoal>clean install</defaultGoal>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>src/main/java</directory>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.0</version>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
@@ -56,7 +48,7 @@
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>true</createDependencyReducedPom>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
@@ -64,10 +56,11 @@
<shadedPattern>com.comphenix.net.sf</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.bukkit:craftbukkit</exclude>
<exclude>org.spigotmc:spigot</exclude>
<exclude>org.spigotmc:spigot-api</exclude>
<exclude>junit:junit</exclude>
</excludes>
</artifactSet>
@@ -75,101 +68,155 @@
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<version>3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<manifestEntries>
<Main-Class>com.comphenix.protocol.Application</Main-Class>
<Implementation-Title>ProtocolLib</Implementation-Title>
<Implementation-Version>${project.version}${project.build.number}</Implementation-Version>
<Implementation-Vendor>dmulloy2</Implementation-Vendor>
</manifestEntries>
</archive>
<finalName>ProtocolLib</finalName>
</configuration>
</plugin>
<!--<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<phase>package</phase>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
<goal>javadoc</goal>
</goals>
</execution>
</executions>
</plugin>-->
</plugins>
</build>
<reporting>
<!--<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<threshold>High</threshold>
<effort>Default</effort>
</configuration>
<configuration>
<threshold>High</threshold>
<effort>Default</effort>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.7</version>
<configuration>
<failOnError>false</failOnError>
<links>
<link>http://download.oracle.com/javase/6/docs/api/</link>
</links>
</configuration>
</plugin>
</plugins>
</reporting>
</reporting>-->
<profiles>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>jenkins</id>
<activation>
<property>
<name>env.BUILD_NUMBER</name>
</property>
</activation>
<properties>
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
</properties>
</profile>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
<name>ProtocolLib</name>
<scm>
<connection>scm:git:git://github.com/aadnk/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:aadnk/ProtocolLib.git</developerConnection>
<url>https://github.com/aadnk/ProtocolLib</url>
<connection>scm:git:git://github.com/dmulloy2/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:dmulloy2/ProtocolLib.git</developerConnection>
<url>https://github.com/dmulloy2/ProtocolLib</url>
</scm>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
@@ -177,39 +224,77 @@
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dmulloy2</id>
<name>Dan Mulloy</name>
<url>http://shadowvolt.com/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
</roles>
</developer>
<developer>
<id>aadnk</id>
<name>Kristian S. Stangeland</name>
<email>kr_stang@hotmail.com</email>
<url>http://comphenix.net/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
<role>former author</role>
</roles>
<timezone>1</timezone>
</developer>
</developers>
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.3.2-R1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.comphenix.executors</groupId>
<artifactId>BukkitExecutors</artifactId>
<version>1.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -17,14 +17,42 @@
package com.comphenix.protocol;
import org.bukkit.Bukkit;
/**
* Ignore this class.
*
* @author Kristian
*/
public class Application {
private static Thread mainThread;
private static boolean primaryMethod = true;
public static void main(String[] args) {
// For now, though we might consider making a proper application
System.out.println("This is a Bukkit library. Place it in the plugin-folder and restart the server!");
}
/**
* Determine if we are running on the main thread.
* @return TRUE if we are, FALSE otherwise.
*/
public static boolean isPrimaryThread() {
if (primaryMethod) {
try {
return Bukkit.isPrimaryThread();
} catch (LinkageError e) {
primaryMethod = false;
}
}
// Fallback method
return Thread.currentThread().equals(mainThread);
}
/**
* Register the calling thread as the primary thread.
*/
static void registerPrimaryThread() {
mainThread = Thread.currentThread();
}
}
@@ -2,27 +2,28 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.util.Set;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.async.AsyncListenerHandler;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
@@ -35,9 +36,11 @@ public interface AsynchronousManager {
/**
* Registers an asynchronous packet handler.
* <p>
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted.
* <p>
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
* @param listener - the packet listener that will recieve these asynchronous events.
* @return An asynchrouns handler.
* @param listener - the packet listener that will receive these asynchronous events.
* @return An asynchronous handler.
*/
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
@@ -47,6 +50,12 @@ public interface AsynchronousManager {
*/
public abstract void unregisterAsyncHandler(AsyncListenerHandler handler);
/**
* Unregisters and closes the first asynchronous handler associated with the given listener.
* @param listener - asynchronous listener
*/
public abstract void unregisterAsyncHandler(PacketListener listener);
/**
* Unregisters every asynchronous handler associated with this plugin.
* @param plugin - the original plugin.
@@ -54,18 +63,38 @@ public interface AsynchronousManager {
public void unregisterAsyncHandlers(Plugin plugin);
/**
* Retrieves a immutable set containing the ID of the sent server packets that will be
* Retrieves a immutable set containing the ID of the sent server packets that will be
* observed by the asynchronous listeners.
* <p>
* Deprecated: Use {@link #getSendingTypes()} instead.
* @return Every filtered server packet.
*/
@Deprecated
public abstract Set<Integer> getSendingFilters();
/**
* Retrieves a immutable set containing the types of the sent server packets that will be
* observed by the asynchronous listeners.
* @return Every filtered server packet.
*/
public abstract Set<PacketType> getSendingTypes();
/**
* Retrieves a immutable set containing the ID of the recieved client packets that will be
* <p>
* Deprecated: Use {@link #getReceivingTypes()} instead.
* observed by the asynchronous listeners.
* @return Every filtered client packet.
*/
@Deprecated
public abstract Set<Integer> getReceivingFilters();
/**
* Retrieves a immutable set containing the types of the received client packets that will be
* observed by the asynchronous listeners.
* @return Every filtered client packet.
*/
public abstract Set<PacketType> getReceivingTypes();
/**
* Determine if a given synchronous packet has asynchronous listeners.
@@ -81,10 +110,10 @@ public interface AsynchronousManager {
public abstract PacketStream getPacketStream();
/**
* Retrieve the default error logger.
* @return Default logger.
* Retrieve the default error reporter.
* @return Default reporter.
*/
public abstract Logger getLogger();
public abstract ErrorReporter getErrorReporter();
/**
* Remove listeners, close threads and transmit every delayed packet.
@@ -99,4 +128,28 @@ public interface AsynchronousManager {
* @param packet - packet to signal.
*/
public abstract void signalPacketTransmission(PacketEvent packet);
/**
* Register a synchronous listener that handles packets when they time out.
* @param listener - synchronous listener that will handle timed out packets.
*/
public abstract void registerTimeoutHandler(PacketListener listener);
/**
* Unregisters a given timeout listener.
* @param listener - the timeout listener to unregister.
*/
public abstract void unregisterTimeoutHandler(PacketListener listener);
/**
* Get a immutable set of every registered timeout handler.
* @return Set of every registered timeout handler.
*/
public abstract Set<PacketListener> getTimeoutHandlers();
/**
* Get an immutable set of every registered asynchronous packet listener.
* @return Set of every asynchronous packet listener.
*/
public abstract Set<PacketListener> getAsyncHandlers();
}
@@ -0,0 +1,170 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import com.comphenix.protocol.async.AsyncListenerHandler;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodUtils;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.compiler.StructureCompiler;
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
/**
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
*
* @author Kristian
*/
class CleanupStaticMembers {
// Reports
public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
private ClassLoader loader;
private ErrorReporter reporter;
public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) {
this.loader = loader;
this.reporter = reporter;
}
/**
* Ensure that the previous ClassLoader is not leaking.
*/
public void resetAll() {
// This list must always be updated
@SuppressWarnings("deprecation")
Class<?>[] publicClasses = {
AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class,
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
BackgroundCompiler.class, StructureCompiler.class,
ObjectWriter.class, Packets.Server.class, Packets.Client.class,
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class,
AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class,
MinecraftReflection.class, NbtBinarySerializer.class
};
String[] internalClasses = {
"com.comphenix.protocol.events.SerializedOfflinePlayer",
"com.comphenix.protocol.injector.player.InjectedServerConnection",
"com.comphenix.protocol.injector.player.NetworkFieldInjector",
"com.comphenix.protocol.injector.player.NetworkObjectInjector",
"com.comphenix.protocol.injector.player.NetworkServerInjector",
"com.comphenix.protocol.injector.player.PlayerInjector",
"com.comphenix.protocol.injector.EntityUtilities",
"com.comphenix.protocol.injector.packet.PacketRegistry",
"com.comphenix.protocol.injector.packet.PacketInjector",
"com.comphenix.protocol.injector.packet.ReadPacketModifier",
"com.comphenix.protocol.injector.StructureCache",
"com.comphenix.protocol.reflect.compiler.BoxingHelper",
"com.comphenix.protocol.reflect.compiler.MethodDescriptor",
"com.comphenix.protocol.wrappers.nbt.WrappedElement",
};
resetClasses(publicClasses);
resetClasses(getClasses(loader, internalClasses));
}
private void resetClasses(Class<?>[] classes) {
// Reset each class one by one
for (Class<?> clazz : classes) {
resetClass(clazz);
}
}
private void resetClass(Class<?> clazz) {
for (Field field : clazz.getFields()) {
Class<?> type = field.getType();
// Only check static non-primitive fields. We also skip strings.
if (Modifier.isStatic(field.getModifiers()) &&
!type.isPrimitive() && !type.equals(String.class) &&
!type.equals(ReportType.class)) {
try {
setFinalStatic(field, null);
} catch (IllegalAccessException e) {
// Just inform the player
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
);
e.printStackTrace();
}
}
}
}
// HACK! HAACK!
private static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException {
int modifier = field.getModifiers();
boolean isFinal = Modifier.isFinal(modifier);
Field modifiersField = isFinal ? FieldUtils.getField(Field.class, "modifiers", true) : null;
// We have to remove the final field first
if (isFinal) {
FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true);
}
// Now we can safely modify the field
FieldUtils.writeStaticField(field, newValue, true);
// Revert modifier
if (isFinal) {
FieldUtils.writeField(modifiersField, field, modifier, true);
}
}
private Class<?>[] getClasses(ClassLoader loader, String[] names) {
List<Class<?>> output = new ArrayList<Class<?>>();
for (String name : names) {
try {
output.add(loader.loadClass(name));
} catch (ClassNotFoundException e) {
// Warn the user
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_UNLOAD_CLASS).error(e).messageParam(name));
}
}
return output.toArray(new Class<?>[0]);
}
}
@@ -0,0 +1,156 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
/**
* Base class for all our commands.
*
* @author Kristian
*/
abstract class CommandBase implements CommandExecutor {
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
public static final String PERMISSION_ADMIN = "protocol.admin";
private String permission;
private String name;
private int minimumArgumentCount;
protected ErrorReporter reporter;
public CommandBase(ErrorReporter reporter, String permission, String name) {
this(reporter, permission, name, 0);
}
public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) {
this.reporter = reporter;
this.name = name;
this.permission = permission;
this.minimumArgumentCount = minimumArgumentCount;
}
@Override
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
try {
// Make sure we're dealing with the correct command
if (!command.getName().equalsIgnoreCase(name)) {
reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
return false;
}
if (permission != null && !sender.hasPermission(permission)) {
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
return true;
}
// Check argument length
if (args != null && args.length >= minimumArgumentCount) {
return handleCommand(sender, args);
} else {
sender.sendMessage(ChatColor.RED + "Insufficient arguments. You need at least " + minimumArgumentCount);
return false;
}
} catch (Throwable ex) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_COMMAND_ERROR).error(ex).messageParam(name).callerParam(sender, label, args)
);
return true;
}
}
/**
* Parse a boolean value at the head of the queue.
* @param arguments - the queue of arguments.
* @param parameterName - the parameter name we will match.
* @return The parsed boolean, or NULL if not valid.
*/
protected Boolean parseBoolean(Deque<String> arguments, String parameterName) {
Boolean result = null;
if (!arguments.isEmpty()) {
String arg = arguments.peek();
if (arg.equalsIgnoreCase("true") || arg.equalsIgnoreCase("on"))
result = true;
else if (arg.equalsIgnoreCase(parameterName))
result = true;
else if (arg.equalsIgnoreCase("false") || arg.equalsIgnoreCase("off"))
result = false;
}
if (result != null)
arguments.poll();
return result;
}
/**
* Create a queue from a sublist of a given array.
* @param args - the source array.
* @param start - the starting index.
* @return A queue that contains every element in the array, starting at the given index.
*/
protected Deque<String> toQueue(String[] args, int start) {
return new ArrayDeque<String>(Arrays.asList(args).subList(start, args.length));
}
/**
* Retrieve the permission necessary to execute this command.
* @return The permission, or NULL if not needed.
*/
public String getPermission() {
return permission;
}
/**
* Retrieve the primary name of this command.
* @return Primary name.
*/
public String getName() {
return name;
}
/**
* Retrieve the error reporter.
* @return Error reporter.
*/
protected ErrorReporter getReporter() {
return reporter;
}
/**
* Main implementation of this command.
* @param sender - command sender.
* @param args - input arguments.
* @return TRUE if the command was successfully handled, FALSE otherwise.
*/
protected abstract boolean handleCommand(CommandSender sender, String[] args);
}
@@ -0,0 +1,481 @@
package com.comphenix.protocol;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.conversations.Conversable;
import org.bukkit.conversations.Conversation;
import org.bukkit.conversations.ConversationAbandonedEvent;
import org.bukkit.conversations.ConversationAbandonedListener;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.ConversationFactory;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.MultipleLinesPrompt.MultipleConversationCanceller;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.Sets;
/**
* A command to apply JavaScript filtering to the packet command.
*
* @author Kristian
*/
public class CommandFilter extends CommandBase {
public static final ReportType REPORT_FALLBACK_ENGINE = new ReportType("Falling back to the Rhino engine.");
public static final ReportType REPORT_CANNOT_LOAD_FALLBACK_ENGINE = new ReportType("Could not load Rhino either. Please upgrade your JVM or OS.");
public static final ReportType REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE = new ReportType("Unable to initialize packages for JavaScript engine.");
public static final ReportType REPORT_FILTER_REMOVED_FOR_ERROR = new ReportType("Removing filter %s for causing %s.");
public static final ReportType REPORT_CANNOT_HANDLE_CONVERSATION = new ReportType("Cannot handle conversation.");
public interface FilterFailedHandler{
/**
* Invoked when a given filter has failed.
* @param event - the packet event.
* @param filter - the filter that failed.
* @param ex - the failure.
* @return TRUE to keep processing this filter, FALSE to remove it.
*/
public boolean handle(PacketEvent event, Filter filter, Exception ex);
}
/**
* Possible sub commands.
*
* @author Kristian
*/
private enum SubCommand {
ADD, REMOVE;
}
/**
* A filter that will be used to process a packet event.
* @author Kristian
*/
public static class Filter {
private final String name;
private final String predicate;
private final Set<PacketType> packets;
/**
* Construct a new immutable filter.
* @param name - the unique name of the filter.
* @param predicate - the JavaScript predicate that will be used to filter packet events.
* @param packets - a list of packet types this filter applies to.
*/
public Filter(String name, String predicate, Set<PacketType> packets) {
this.name = name;
this.predicate = predicate;
this.packets = Sets.newHashSet(packets);
}
/**
* Retrieve the unique name of the filter.
* @return Unique name of the filter.
*/
public String getName() {
return name;
}
/**
* Retrieve the JavaScript predicate that will be used to filter packet events.
* @return Predicate itself.
*/
public String getPredicate() {
return predicate;
}
/**
* Retrieve a copy of the set of packets this filter applies to.
* @return Set of packets this filter applies to.
*/
public Set<PacketType> getRanges() {
return Sets.newHashSet(packets);
}
/**
* Determine whether or not a packet event needs to be passed to this filter.
* @param event - the event to test.
* @return TRUE if it does, FALSE otherwise.
*/
private boolean isApplicable(PacketEvent event) {
return packets.contains(event.getPacketType());
}
/**
* Evaluate the current filter using the provided ScriptEngine as context.
* <p>
* This context may be modified with additional code.
* @param context - the current script context.
* @param event - the packet event to evaluate.
* @return TRUE to pass this packet event on to the debug listeners, FALSE otherwise.
* @throws ScriptException If the compilation failed or the filter is not valid.
*/
public boolean evaluate(ScriptEngine context, PacketEvent event) throws ScriptException {
if (!isApplicable(event))
return true;
// Ensure that the predicate has been compiled
compile(context);
try {
Object result = ((Invocable) context).invokeFunction(name, event, event.getPacket().getHandle());
if (result instanceof Boolean)
return (Boolean) result;
else
throw new ScriptException("Filter result wasn't a boolean: " + result);
} catch (NoSuchMethodException e) {
// Must be a fault with the script engine itself
throw new IllegalStateException("Unable to compile " + name + " into current script engine.", e);
}
}
/**
* Force the compilation of a specific filter.
* @param context - the current script context.
* @throws ScriptException If the compilation failed.
*/
public void compile(ScriptEngine context) throws ScriptException {
if (context.get(name) == null) {
context.eval("var " + name + " = function(event, packet) {\n" + predicate);
}
}
/**
* Clean up all associated code from this filter in the provided script engine.
* @param context - the current script context.
*/
public void close(ScriptEngine context) {
context.put(name, null);
}
}
private class CompilationSuccessCanceller implements MultipleConversationCanceller {
@Override
public boolean cancelBasedOnInput(ConversationContext context, String in) {
throw new UnsupportedOperationException("Cannot cancel on the last line alone.");
}
@Override
public void setConversation(Conversation conversation) {
// Ignore
}
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine, StringBuilder lines, int lineCount) {
try {
engine.eval("function(event, packet) {\n" + lines.toString());
// It compiles - accept the filter!
return true;
} catch (ScriptException e) {
// We also have the function() line
int realLineCount = lineCount + 1;
// Only possible to recover from an error on the last line.
return e.getLineNumber() < realLineCount;
}
}
@Override
public CompilationSuccessCanceller clone() {
return new CompilationSuccessCanceller();
}
}
/**
* Name of this command.
*/
public static final String NAME = "filter";
// Default error handler
private FilterFailedHandler defaultFailedHandler;
// Currently registered filters
private List<Filter> filters = new ArrayList<Filter>();
// Owner plugin
private final Plugin plugin;
// Whether or not the command is enabled
private ProtocolConfig config;
// Script engine
private ScriptEngine engine;
private boolean uninitialized;
public CommandFilter(ErrorReporter reporter, Plugin plugin, ProtocolConfig config) {
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 2);
this.plugin = plugin;
this.config = config;
// Tell the filter system to initialize the script first chance it gets
this.uninitialized = true;
}
private void initalizeScript() {
try {
// First attempt
initializeEngine();
// Oh for ..
if (!isInitialized()) {
throw new ScriptException("A JavaScript engine could not be found.");
} else {
plugin.getLogger().info("Loaded command filter engine.");
}
} catch (ScriptException e1) {
// It's not a huge deal
printPackageWarning(e1);
if (!config.getScriptEngineName().equals("rhino")) {
reporter.reportWarning(this, Report.newBuilder(REPORT_FALLBACK_ENGINE));
config.setScriptEngineName("rhino");
config.saveAll();
try {
initializeEngine();
if (!isInitialized()) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_FALLBACK_ENGINE));
}
} catch (ScriptException e2) {
// And again ..
printPackageWarning(e2);
}
}
}
}
private void printPackageWarning(ScriptException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE).error(e));
}
/**
* Initialize the current configured engine.
* @throws ScriptException If we are unable to import packages.
*/
private void initializeEngine() throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
engine = manager.getEngineByName(config.getScriptEngineName());
// Import useful packages
if (engine != null) {
engine.eval("importPackage(org.bukkit);");
engine.eval("importPackage(com.comphenix.protocol.reflect);");
}
}
/**
* Determine if the filter engine has been successfully initialized.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isInitialized() {
return engine != null;
}
private FilterFailedHandler getDefaultErrorHandler() {
// No need to create a new object every time
if (defaultFailedHandler == null) {
defaultFailedHandler = new FilterFailedHandler() {
@Override
public boolean handle(PacketEvent event, Filter filter, Exception ex) {
reporter.reportMinimal(plugin, "filterEvent(PacketEvent)", ex, event);
reporter.reportWarning(this,
Report.newBuilder(REPORT_FILTER_REMOVED_FOR_ERROR).messageParam(filter.getName(), ex.getClass().getSimpleName())
);
return false;
}
};
}
return defaultFailedHandler;
}
/**
* Determine whether or not to pass the given packet event to the packet listeners.
* <p>
* Uses a default filter failure handler that simply prints the error message and removes the filter.
* @param event - the event.
* @return TRUE if we should, FALSE otherwise.
*/
public boolean filterEvent(PacketEvent event) {
return filterEvent(event, getDefaultErrorHandler());
}
/**
* Determine whether or not to pass the given packet event to the packet listeners.
* @param event - the event.
* @param handler - failure handler.
* @return TRUE if we should, FALSE otherwise.
*/
public boolean filterEvent(PacketEvent event, FilterFailedHandler handler) {
for (Iterator<Filter> it = filters.iterator(); it.hasNext(); ) {
Filter filter = it.next();
try {
if (!filter.evaluate(engine, event)) {
return false;
}
} catch (Exception ex) {
if (!handler.handle(event, filter, ex)) {
it.remove();
}
}
}
// Pass!
return true;
}
/**
* Initialize the script engine if necessary.
*/
private void checkScriptStatus() {
// Start the engine
if (uninitialized) {
uninitialized = false;
initalizeScript();
}
}
/*
* Description: Adds or removes a simple packet filter.
Usage: /<command> add|remove name [packet IDs]
*/
@Override
protected boolean handleCommand(CommandSender sender, String[] args) {
checkScriptStatus();
if (!config.isDebug()) {
sender.sendMessage(ChatColor.RED + "Debug mode must be enabled in the configuration first!");
return true;
}
if (!isInitialized()) {
sender.sendMessage(ChatColor.RED + "JavaScript engine was not present. Filter system is disabled.");
return true;
}
final SubCommand command = parseCommand(args, 0);
final String name = args[1];
switch (command) {
case ADD:
// Never overwrite an existing filter
if (findFilter(name) != null) {
sender.sendMessage(ChatColor.RED + "Filter " + name + " already exists. Remove it first.");
return true;
}
// Prepare the input to the packet type parser
Deque<String> rangeArguments = toQueue(args, 2);
final PacketTypeParser parser = new PacketTypeParser();
final Set<PacketType> packets = parser.parseTypes(rangeArguments, PacketTypeParser.DEFAULT_MAX_RANGE);
sender.sendMessage("Enter filter program ('}' to complete or CANCEL):");
// Make sure we can use the conversable interface
if (sender instanceof Conversable) {
final MultipleLinesPrompt prompt =
new MultipleLinesPrompt(new CompilationSuccessCanceller(), "function(event, packet) {");
new ConversationFactory(plugin).
withFirstPrompt(prompt).
withEscapeSequence("CANCEL").
withLocalEcho(false).
addConversationAbandonedListener(new ConversationAbandonedListener() {
@Override
public void conversationAbandoned(ConversationAbandonedEvent event) {
try {
final Conversable whom = event.getContext().getForWhom();
if (event.gracefulExit()) {
String predicate = prompt.removeAccumulatedInput(event.getContext());
Filter filter = new Filter(name, predicate, packets);
// Print the last line as well
whom.sendRawMessage(prompt.getPromptText(event.getContext()));
try {
// Force early compilation
filter.compile(engine);
filters.add(filter);
whom.sendRawMessage(ChatColor.GOLD + "Added filter " + name);
} catch (ScriptException e) {
e.printStackTrace();
whom.sendRawMessage(ChatColor.GOLD + "Compilation error: " + e.getMessage());
}
} else {
// Too bad
whom.sendRawMessage(ChatColor.RED + "Cancelled filter.");
}
} catch (Exception e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HANDLE_CONVERSATION).error(e).callerParam(event)
);
}
}
}).
buildConversation((Conversable) sender).
begin();
} else {
sender.sendMessage(ChatColor.RED + "Only console and players are supported!");
}
break;
case REMOVE:
Filter filter = findFilter(name);
// See if it exists before we remove it
if (filter != null) {
filter.close(engine);
filters.remove(filter);
sender.sendMessage(ChatColor.GOLD + "Removed filter " + name);
} else {
sender.sendMessage(ChatColor.RED + "Unable to find a filter by the name " + name);
}
break;
}
return true;
}
/**
* Lookup a filter by its name.
* @param name - the filter name.
* @return The filter, or NULL if not found.
*/
private Filter findFilter(String name) {
// We'll just use a linear scan for now - we don't expect that many filters
for (Filter filter : filters) {
if (filter.getName().equalsIgnoreCase(name)) {
return filter;
}
}
return null;
}
private SubCommand parseCommand(String[] args, int index) {
String text = args[index].toUpperCase();
try {
return SubCommand.valueOf(text);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.", e);
}
}
}
@@ -0,0 +1,547 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.cglib.proxy.Factory;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.PrettyPrinter;
import com.comphenix.protocol.reflect.PrettyPrinter.ObjectPrinter;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.HexDumper;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;
/**
* Handles the "packet" debug command.
*
* @author Kristian
*/
class CommandPacket extends CommandBase {
public static final ReportType REPORT_CANNOT_SEND_MESSAGE = new ReportType("Cannot send chat message.");
private enum SubCommand {
ADD, REMOVE, NAMES, PAGE;
}
/**
* Name of this command.
*/
public static final String NAME = "packet";
/**
* Number of lines per page.
*/
public static final int PAGE_LINE_COUNT = 9;
/**
* Number of bytes before we do a hex dump.
*/
private static final int HEX_DUMP_THRESHOLD = 256;
private Plugin plugin;
private Logger logger;
private ProtocolManager manager;
private ChatExtensions chatter;
// The main parser
private PacketTypeParser typeParser = new PacketTypeParser();
// Paged message
private Map<CommandSender, List<String>> pagedMessage = new WeakHashMap<CommandSender, List<String>>();
// Current registered packet types
private PacketTypeSet packetTypes = new PacketTypeSet();
private PacketTypeSet extendedTypes = new PacketTypeSet();
// Compare listeners
private PacketTypeSet compareTypes = new PacketTypeSet();
private Map<PacketEvent, String> originalPackets = new MapMaker().weakKeys().makeMap();
// The packet listener
private PacketListener listener;
// Compare listener
private PacketListener compareListener;
// Filter packet events
private CommandFilter filter;
public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, CommandFilter filter, ProtocolManager manager) {
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
this.plugin = plugin;
this.logger = logger;
this.manager = manager;
this.filter = filter;
this.chatter = new ChatExtensions(manager);
}
/**
* Send a message without invoking the packet listeners.
* @param receiver - the player to send it to.
* @param message - the message to send.
* @return TRUE if the message was sent successfully, FALSE otherwise.
*/
public void sendMessageSilently(CommandSender receiver, String message) {
try {
chatter.sendMessageSilently(receiver, message);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(receiver, message)
);
}
}
/**
* Broadcast a message without invoking any packet listeners.
* @param message - message to send.
* @param permission - permission required to receieve the message. NULL to target everyone.
*/
public void broadcastMessageSilently(String message, String permission) {
try {
chatter.broadcastMessageSilently(message, permission);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(message, permission)
);
}
}
private void printPage(CommandSender sender, int pageIndex) {
List<String> paged = pagedMessage.get(sender);
// Make sure the player has any pages
if (paged != null) {
int lastPage = ((paged.size() - 1) / PAGE_LINE_COUNT) + 1;
for (int i = PAGE_LINE_COUNT * (pageIndex - 1); i < PAGE_LINE_COUNT * pageIndex; i++) {
if (i < paged.size()) {
sendMessageSilently(sender, " " + paged.get(i));
}
}
// More data?
if (pageIndex < lastPage) {
sendMessageSilently(sender, "Send /packet page " + (pageIndex + 1) + " for the next page.");
}
} else {
sendMessageSilently(sender, ChatColor.RED + "No pages found.");
}
}
/*
* Description: Adds or removes a simple packet listener.
Usage: /<command> add|remove client|server|both [ID start] [ID stop] [detailed]
*/
@Override
protected boolean handleCommand(CommandSender sender, String[] args) {
try {
Deque<String> arguments = new ArrayDeque<String>(Arrays.asList(args));
SubCommand subCommand = parseCommand(arguments);
// Commands with different parameters
if (subCommand == SubCommand.PAGE) {
if (args.length <= 1) {
sendMessageSilently(sender, ChatColor.RED + "Must specify a page index.");
return true;
}
int page = Integer.parseInt(args[1]);
if (page > 0)
printPage(sender, page);
else
sendMessageSilently(sender, ChatColor.RED + "Page index must be greater than zero.");
return true;
}
Set<PacketType> types = typeParser.parseTypes(arguments, PacketTypeParser.DEFAULT_MAX_RANGE);
Boolean detailed = parseBoolean(arguments, "detailed");
Boolean compare = parseBoolean(arguments, "compare");
// Notify user
if (typeParser.getLastProtocol() == null) {
sender.sendMessage(ChatColor.YELLOW + "Warning: Missing protocol (PLAY, etc) - assuming legacy IDs.");
}
if (arguments.size() > 0) {
throw new IllegalArgumentException("Cannot parse " + arguments);
}
// The last elements are optional
if (detailed == null) {
detailed = false;
}
if (compare == null) {
compare = false;
} else {
// This is implied - we cannot compare content without going detailed
detailed = true;
}
// Perform commands
if (subCommand == SubCommand.ADD) {
// The add command is dangerous - don't default on the connection side
if (args.length == 1) {
sender.sendMessage(ChatColor.RED + "Please specify a connection side.");
return false;
}
executeAddCommand(sender, types, detailed, compare);
} else if (subCommand == SubCommand.REMOVE) {
executeRemoveCommand(sender, types);
} else if (subCommand == SubCommand.NAMES) {
executeNamesCommand(sender, types);
}
} catch (NumberFormatException e) {
sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage());
} catch (IllegalArgumentException e) {
sendMessageSilently(sender, ChatColor.RED + e.getMessage());
}
return true;
}
private void executeAddCommand(CommandSender sender, Set<PacketType> addition, boolean detailed, boolean compare) {
packetTypes.addAll(addition);
// Also mark these types as "detailed"
if (detailed) {
extendedTypes.addAll(addition);
}
// Whether or not to compare the packet with the initial state
if (compare) {
compareTypes.addAll(addition);
}
updatePacketListener();
sendMessageSilently(sender, ChatColor.YELLOW + "Added listener " + getWhitelistInfo(listener));
}
private void executeRemoveCommand(CommandSender sender, Set<PacketType> removal) {
packetTypes.removeAll(removal);
extendedTypes.removeAll(removal);
compareTypes.removeAll(removal);
updatePacketListener();
sendMessageSilently(sender, ChatColor.YELLOW + "Removing packet types.");
}
private void executeNamesCommand(CommandSender sender, Set<PacketType> types) {
List<String> messages = new ArrayList<String>();
// Print the equivalent name of every given ID
for (PacketType type : types) {
messages.add(ChatColor.YELLOW + type.toString());
}
if (sender instanceof Player && messages.size() > 0 && messages.size() > PAGE_LINE_COUNT) {
// Divide the messages into chuncks
pagedMessage.put(sender, messages);
printPage(sender, 1);
} else {
// Just print the damn thing
for (String message : messages) {
sendMessageSilently(sender, message);
}
}
}
/**
* Retrieve whitelist information about a given listener.
* @param listener - the given listener.
* @return Whitelist information.
*/
private String getWhitelistInfo(PacketListener listener) {
boolean sendingEmpty = ListeningWhitelist.isEmpty(listener.getSendingWhitelist());
boolean receivingEmpty = ListeningWhitelist.isEmpty(listener.getReceivingWhitelist());
if (!sendingEmpty && !receivingEmpty)
return String.format("Sending: %s, Receiving: %s", listener.getSendingWhitelist(), listener.getReceivingWhitelist());
else if (!sendingEmpty)
return listener.getSendingWhitelist().toString();
else if (!receivingEmpty)
return listener.getReceivingWhitelist().toString();
else
return "[None]";
}
private Set<PacketType> filterTypes(Set<PacketType> types, Sender sender) {
Set<PacketType> result = Sets.newHashSet();
for (PacketType type : types) {
if (type.getSender() == sender) {
result.add(type);
}
}
return result;
}
public PacketListener createPacketListener(Set<PacketType> type) {
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
types(filterTypes(type, Sender.SERVER)).
gamePhaseBoth().
monitor().
build();
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
types(filterTypes(type, Sender.CLIENT)).
monitor().
build();
return new PacketListener() {
@Override
public void onPacketSending(PacketEvent event) {
if (filter.filterEvent(event)) {
printInformation(event);
}
}
@Override
public void onPacketReceiving(PacketEvent event) {
if (filter.filterEvent(event)) {
printInformation(event);
}
}
private void printInformation(PacketEvent event) {
String verb = event.isServerPacket() ? "Sent" : "Received";
String format = event.isServerPacket() ?
"%s %s to %s" :
"%s %s from %s";
String shortDescription = String.format(format,
event.isCancelled() ? "Cancelled" : verb,
event.getPacketType(),
event.getPlayer().getName()
);
// Detailed will print the packet's content too
if (extendedTypes.contains(event.getPacketType())) {
try {
String original = originalPackets.remove(event);
// Also print original
if (original != null) {
logger.info("Initial packet:\n" + original + " -> ");
}
logger.info(shortDescription + ":\n" + getPacketDescription(
event.getPacket())
);
} catch (IllegalAccessException e) {
logger.log(Level.WARNING, "Unable to use reflection.", e);
}
} else {
logger.info(shortDescription + ".");
}
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return serverList;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return clientList;
}
@Override
public Plugin getPlugin() {
return plugin;
}
};
}
public PacketListener createCompareListener(Set<PacketType> type) {
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
types(filterTypes(type, Sender.SERVER)).
gamePhaseBoth().
lowest().
build();
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
types(filterTypes(type, Sender.CLIENT)).
lowest().
build();
return new PacketListener() {
@Override
public void onPacketSending(PacketEvent event) {
savePacketState(event);
}
@Override
public void onPacketReceiving(PacketEvent event) {
savePacketState(event);
}
/**
* Save the original value.
* @param event - the event with the packet to save.
*/
private void savePacketState(PacketEvent event) {
try {
originalPackets.put(event, getPacketDescription(event.getPacket()));
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot read packet.", e);
}
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return serverList;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return clientList;
}
@Override
public Plugin getPlugin() {
return plugin;
}
};
}
/**
* Retrieve a detailed string representation of the given packet.
* @param packetContainer - the packet to describe.
* @return The detailed description.
* @throws IllegalAccessException An error occured.
*/
public String getPacketDescription(PacketContainer packetContainer) throws IllegalAccessException {
Object packet = packetContainer.getHandle();
Class<?> clazz = packet.getClass();
// Get the first Minecraft super class
while (clazz != null && clazz != Object.class &&
(!MinecraftReflection.isMinecraftClass(clazz) ||
Factory.class.isAssignableFrom(clazz))) {
clazz = clazz.getSuperclass();
}
return PrettyPrinter.printObject(packet, clazz, MinecraftReflection.getPacketClass(), PrettyPrinter.RECURSE_DEPTH, new ObjectPrinter() {
@Override
public boolean print(StringBuilder output, Object value) {
// Special case
if (value instanceof byte[]) {
byte[] data = (byte[]) value;
if (data.length > HEX_DUMP_THRESHOLD) {
output.append("[");
HexDumper.defaultDumper().appendTo(output, data);
output.append("]");
return true;
}
} else if (value != null) {
EquivalentConverter<Object> converter = findConverter(value.getClass());
if (converter != null) {
output.append(converter.getSpecific(value));
return true;
}
}
return false;
}
});
}
/**
* Retrieve the closest equivalent converter to a specific class.
* @param clazz - the class.
* @return The closest converter, or NULL if not found,
*/
private EquivalentConverter<Object> findConverter(Class<?> clazz) {
Map<Class<?>, EquivalentConverter<Object>> converters = BukkitConverters.getConvertersForGeneric();
while (clazz != null) {
EquivalentConverter<Object> result = converters.get(clazz);
if (result != null)
return result;
else
clazz = clazz.getSuperclass();
}
return null;
}
public PacketListener updatePacketListener() {
if (listener != null) {
manager.removePacketListener(listener);
}
if (compareListener != null) {
manager.removePacketListener(compareListener);
}
// Register the new listeners
listener = createPacketListener(packetTypes.values());
compareListener = createCompareListener(compareTypes.values());
manager.addPacketListener(listener);
manager.addPacketListener(compareListener);
return listener;
}
private SubCommand parseCommand(Deque<String> arguments) {
String text = arguments.poll().toLowerCase();
// Parse this too
if ("add".startsWith(text))
return SubCommand.ADD;
else if ("remove".startsWith(text))
return SubCommand.REMOVE;
else if ("names".startsWith(text))
return SubCommand.NAMES;
else if ("page".startsWith(text))
return SubCommand.PAGE;
else
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.");
}
}
@@ -0,0 +1,272 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;
import java.util.logging.Level;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.timing.TimedListenerManager;
import com.comphenix.protocol.timing.TimingReportGenerator;
import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.Closer;
/**
* Handles the "protocol" administration command.
*
* @author Kristian
*/
class CommandProtocol extends CommandBase {
/**
* Name of this command.
*/
public static final String NAME = "protocol";
private Plugin plugin;
private Updater updater;
private ProtocolConfig config;
public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) {
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
this.plugin = plugin;
this.updater = updater;
this.config = config;
}
@Override
protected boolean handleCommand(CommandSender sender, String[] args) {
String subCommand = args[0];
// Only return TRUE if we executed the correct command
if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload")) {
reloadConfiguration(sender);
} else if (subCommand.equalsIgnoreCase("check")) {
checkVersion(sender, true);
} else if (subCommand.equalsIgnoreCase("update")) {
updateVersion(sender, true);
} else if (subCommand.equalsIgnoreCase("timings")) {
toggleTimings(sender, args);
} else if (subCommand.equalsIgnoreCase("listeners")) {
printListeners(sender);
} else if (subCommand.equalsIgnoreCase("version")) {
printVersion(sender);
} else if (subCommand.equalsIgnoreCase("dump")) {
dump(sender);
} else {
return false;
}
return true;
}
public void checkVersion(final CommandSender sender, boolean command) {
performUpdate(sender, UpdateType.NO_DOWNLOAD, command);
}
public void updateVersion(final CommandSender sender, boolean command) {
performUpdate(sender, UpdateType.DEFAULT, command);
}
// Display every listener on the server
private void printListeners(final CommandSender sender) {
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
sender.sendMessage(ChatColor.GOLD + "Packet listeners:");
for (PacketListener listener : manager.getPacketListeners()) {
sender.sendMessage(ChatColor.GOLD + " - " + listener);
}
// Along with every asynchronous listener
sender.sendMessage(ChatColor.GOLD + "Asynchronous listeners:");
for (PacketListener listener : manager.getAsynchronousManager().getAsyncHandlers()) {
sender.sendMessage(ChatColor.GOLD + " - " + listener);
}
}
private void performUpdate(final CommandSender sender, UpdateType type, final boolean command) {
if (updater.isChecking()) {
sender.sendMessage(ChatColor.RED + "Already checking for an update.");
return;
}
// Perform on an async thread
Runnable notify = new Runnable() {
@Override
public void run() {
if (command) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
String remoteVersion = updater.getRemoteVersion();
if (remoteVersion != null) {
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
}
} else if (updater.shouldNotify() || config.isDebug()) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
}
updater.removeListener(this);
updateFinished();
}
};
updater.start(type);
updater.addListener(notify);
}
private void toggleTimings(CommandSender sender, String[] args) {
TimedListenerManager manager = TimedListenerManager.getInstance();
boolean state = !manager.isTiming(); // toggle
// Parse the boolean parameter
if (args.length == 2) {
Boolean parsed = parseBoolean(toQueue(args, 2), "start");
if (parsed != null) {
state = parsed;
} else {
sender.sendMessage(ChatColor.RED + "Specify a state: ON or OFF.");
return;
}
} else if (args.length > 2) {
sender.sendMessage(ChatColor.RED + "Too many parameters.");
return;
}
// Now change the state
if (state) {
if (manager.startTiming())
sender.sendMessage(ChatColor.GOLD + "Started timing packet listeners.");
else
sender.sendMessage(ChatColor.RED + "Packet timing already started.");
} else {
if (manager.stopTiming()) {
saveTimings(manager);
sender.sendMessage(ChatColor.GOLD + "Stopped and saved result in plugin folder.");
} else {
sender.sendMessage(ChatColor.RED + "Packet timing already stopped.");
}
}
}
private void saveTimings(TimedListenerManager manager) {
try {
File destination = new File(plugin.getDataFolder(), "Timings - " + System.currentTimeMillis() + ".txt");
TimingReportGenerator generator = new TimingReportGenerator();
// Print to a text file
generator.saveTo(destination, manager);
manager.clear();
} catch (IOException e) {
reporter.reportMinimal(plugin, "saveTimings()", e);
}
}
/**
* Prevent further automatic updates until the next delay.
*/
public void updateFinished() {
long currentTime = System.currentTimeMillis() / ProtocolLibrary.MILLI_PER_SECOND;
config.setAutoLastTime(currentTime);
config.saveAll();
}
public void reloadConfiguration(CommandSender sender) {
plugin.reloadConfig();
sender.sendMessage(ChatColor.YELLOW + "Reloaded configuration!");
}
private void printVersion(CommandSender sender) {
PluginDescriptionFile desc = plugin.getDescription();
sender.sendMessage(ChatColor.GREEN + desc.getName() + ChatColor.WHITE + " v" + ChatColor.GREEN + desc.getVersion());
sender.sendMessage(ChatColor.WHITE + "Authors: " + ChatColor.GREEN + "dmulloy2" + ChatColor.WHITE + " and " + ChatColor.GREEN + "Comphenix");
sender.sendMessage(ChatColor.WHITE + "Issues: " + ChatColor.GREEN + "https://github.com/dmulloy2/ProtocolLib/issues");
}
private static SimpleDateFormat FILE_FORMAT;
private static SimpleDateFormat TIMESTAMP_FORMAT;
private void dump(CommandSender sender) {
Closer closer = Closer.create();
if (FILE_FORMAT == null)
FILE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
if (TIMESTAMP_FORMAT == null)
TIMESTAMP_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss");
try {
Date date = new Date();
File file = new File(plugin.getDataFolder(), "dump-" + FILE_FORMAT.format(date) + ".txt");
if (file.exists()) {
file.delete();
}
file.createNewFile();
FileWriter fw = closer.register(new FileWriter(file));
PrintWriter pw = closer.register(new PrintWriter(fw));
pw.println("ProtocolLib Dump");
pw.println("Timestamp: " + TIMESTAMP_FORMAT.format(date));
pw.println();
pw.println("ProtocolLib Version: " + plugin.toString());
pw.println("Bukkit Version: " + plugin.getServer().getBukkitVersion());
pw.println("Server Version: " + plugin.getServer().getVersion());
pw.println("Java Version: " + System.getProperty("java.version"));
pw.println();
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
pw.println("ProtocolLib: " + DetailedErrorReporter.getStringDescription(plugin));
pw.println("Manager: " + DetailedErrorReporter.getStringDescription(manager));
pw.println();
Set<PacketListener> listeners = manager.getPacketListeners();
if (listeners.size() > 0) {
pw.println("Listeners:");
for (PacketListener listener : listeners) {
pw.println(DetailedErrorReporter.getStringDescription(listener));
}
} else {
pw.println("No listeners");
}
sender.sendMessage("Data dump written to " + file.getAbsolutePath());
} catch (IOException ex) {
ProtocolLibrary.log(Level.SEVERE, "Failed to create dump:", ex);
sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!");
} finally {
closer.close();
}
}
}
@@ -0,0 +1,163 @@
package com.comphenix.protocol;
import org.bukkit.conversations.Conversation;
import org.bukkit.conversations.ConversationCanceller;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.ExactMatchConversationCanceller;
import org.bukkit.conversations.Prompt;
import org.bukkit.conversations.StringPrompt;
/**
* Represents a conversation prompt that accepts a list of lines.
*
* @author Kristian
*/
class MultipleLinesPrompt extends StringPrompt {
/**
* Represents a canceller that determines if the multiple lines prompt is finished.
* @author Kristian
*/
public static interface MultipleConversationCanceller extends ConversationCanceller {
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine);
/**
* Determine if the current prompt is done based on the context, last
* line and collected lines.
*
* @param context - current context.
* @param currentLine - current (last) line.
* @param lines - collected lines.
* @param lineCount - number of lines.
* @return TRUE if we are done, FALSE otherwise.
*/
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
StringBuilder lines, int lineCount);
}
/**
* A wrapper class for turning a ConversationCanceller into a MultipleConversationCanceller.
* @author Kristian
*/
private static class MultipleWrapper implements MultipleConversationCanceller {
private ConversationCanceller canceller;
public MultipleWrapper(ConversationCanceller canceller) {
this.canceller = canceller;
}
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine) {
return canceller.cancelBasedOnInput(context, currentLine);
}
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
StringBuilder lines, int lineCount) {
return cancelBasedOnInput(context, currentLine);
}
@Override
public void setConversation(Conversation conversation) {
canceller.setConversation(conversation);
}
@Override
public MultipleWrapper clone() {
return new MultipleWrapper(canceller.clone());
}
}
// Feels a bit like Android
private static final String KEY = "multiple_lines_prompt";
private static final String KEY_LAST = KEY + ".last_line";
private static final String KEY_LINES = KEY + ".linecount";
private final MultipleConversationCanceller endMarker;
private final String initialPrompt;
/**
* Retrieve and remove the current accumulated input.
*
* @param context
* - conversation context.
* @return The accumulated input, or NULL if not found.
*/
public String removeAccumulatedInput(ConversationContext context) {
Object result = context.getSessionData(KEY);
if (result instanceof StringBuilder) {
context.setSessionData(KEY, null);
context.setSessionData(KEY_LINES, null);
return ((StringBuilder) result).toString();
} else {
return null;
}
}
/**
* Construct a multiple lines input prompt with a specific end marker.
* <p>
* This is usually an empty string.
*
* @param endMarker - the end marker.
*/
public MultipleLinesPrompt(String endMarker, String initialPrompt) {
this(new ExactMatchConversationCanceller(endMarker), initialPrompt);
}
/**
* Construct a multiple lines input prompt with a specific end marker implementation.
* <p>
* Note: Use {@link #MultipleLinesPrompt(MultipleConversationCanceller, String)} if implementing a custom canceller.
* @param endMarker - the end marker.
* @param initialPrompt - the initial prompt text.
*/
public MultipleLinesPrompt(ConversationCanceller endMarker, String initialPrompt) {
this.endMarker = new MultipleWrapper(endMarker);
this.initialPrompt = initialPrompt;
}
/**
* Construct a multiple lines input prompt with a specific end marker implementation.
* @param endMarker - the end marker.
* @param initialPrompt - the initial prompt text.
*/
public MultipleLinesPrompt(MultipleConversationCanceller endMarker, String initialPrompt) {
this.endMarker = endMarker;
this.initialPrompt = initialPrompt;
}
@Override
public Prompt acceptInput(ConversationContext context, String in) {
StringBuilder result = (StringBuilder) context.getSessionData(KEY);
Integer count = (Integer) context.getSessionData(KEY_LINES);
// Handle first run
if (result == null)
context.setSessionData(KEY, result = new StringBuilder());
if (count == null)
count = 0;
// Save the last line as well
context.setSessionData(KEY_LAST, in);
context.setSessionData(KEY_LINES, ++count);
result.append(in + "\n");
// And we're done
if (endMarker.cancelBasedOnInput(context, in, result, count))
return Prompt.END_OF_CONVERSATION;
else
return this;
}
@Override
public String getPromptText(ConversationContext context) {
Object last = context.getSessionData(KEY_LAST);
if (last instanceof String)
return (String) last;
else
return initialPrompt;
}
}
@@ -2,16 +2,16 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
@@ -21,7 +21,10 @@ import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.netty.WirePacket;
/**
* Represents a object capable of sending or receiving packets.
@@ -29,25 +32,52 @@ import com.comphenix.protocol.events.PacketContainer;
* @author Kristian
*/
public interface PacketStream {
/**
* Send a packet to the given player.
* @param reciever - the reciever.
* @param receiver - the reciever.
* @param packet - packet to send.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player reciever, PacketContainer packet)
public void sendServerPacket(Player receiver, PacketContainer packet)
throws InvocationTargetException;
/**
* Send a packet to the given player.
* @param reciever - the reciever.
* @param receiver - the reciever.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
throws InvocationTargetException;
/**
* Send a packet to the given player.
* @param receiver - the receiver.
* @param packet - packet to send.
* @param marker - the network marker to use.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters)
throws InvocationTargetException;
/**
* Send a wire packet to the given player.
* @param receiver - the receiver.
* @param id - packet id.
* @param bytes - packet bytes.
* @throws InvocationTargetException if an error occured when sending the packet.
*/
public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException;
/**
* Send a wire packet to the given player.
* @param receiver - the receiver.
* @param packet - packet to send.
* @throws InvocationTargetException if an error occured when sending the packet.
*/
public void sendWirePacket(Player receiver, WirePacket packet) throws InvocationTargetException;
/**
* Simulate recieving a certain packet from a given player.
@@ -56,17 +86,29 @@ public interface PacketStream {
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet)
public void recieveClientPacket(Player sender, PacketContainer packet)
throws IllegalAccessException, InvocationTargetException;
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param filters - whether or not to invoke any packet filters.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
throws IllegalAccessException, InvocationTargetException;
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param marker - the network marker to use.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters)
throws IllegalAccessException, InvocationTargetException;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,187 @@
package com.comphenix.protocol;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.collections.IntegerMap;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
/**
* Retrieve a packet type based on its version and ID, optionally with protocol and sender too.
* @author Kristian
*/
class PacketTypeLookup {
public static class ProtocolSenderLookup {
// Unroll lookup for performance reasons
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> HANDSHAKE_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> GAME_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> GAME_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> STATUS_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> STATUS_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> LOGIN_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> LOGIN_SERVER = IntegerMap.newMap();
/**
* Retrieve the correct integer map for a specific protocol and sender.
* @param protocol - the protocol.
* @param sender - the sender.
* @return The integer map of packets.
*/
public IntegerMap<PacketType> getMap(Protocol protocol, Sender sender) {
switch (protocol) {
case HANDSHAKING:
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
case PLAY:
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
case STATUS:
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
case LOGIN:
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
default:
throw new IllegalArgumentException("Unable to find protocol " + protocol);
}
}
}
public static class ClassLookup {
// Unroll lookup for performance reasons
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<String, PacketType>();
/**
* Retrieve the correct integer map for a specific protocol and sender.
* @param protocol - the protocol.
* @param sender - the sender.
* @return The integer map of packets.
*/
public Map<String, PacketType> getMap(Protocol protocol, Sender sender) {
switch (protocol) {
case HANDSHAKING:
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
case PLAY:
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
case STATUS:
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
case LOGIN:
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
default:
throw new IllegalArgumentException("Unable to find protocol " + protocol);
}
}
}
// Packet IDs from 1.6.4 and below
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> serverLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> clientLookup = new IntegerMap<PacketType>();
// Packets for 1.7.2
private final ProtocolSenderLookup idLookup = new ProtocolSenderLookup();
// Packets for 1.8+
private final ClassLookup classLookup = new ClassLookup();
// Packets based on name
private final Multimap<String, PacketType> nameLookup = HashMultimap.create();
/**
* Add a collection of packet types to the lookup.
* @param types - the types to add.
*/
public PacketTypeLookup addPacketTypes(Iterable<? extends PacketType> types) {
Preconditions.checkNotNull(types, "types cannot be NULL");
for (PacketType type : types) {
int legacy = type.getLegacyId();
// Skip unknown legacy packets
if (legacy != PacketType.UNKNOWN_PACKET) {
if (type.isServer())
serverLookup.put(type.getLegacyId(), type);
if (type.isClient())
clientLookup.put(type.getLegacyId(), type);
legacyLookup.put(type.getLegacyId(), type);
}
// Skip unknown current packets
if (type.getCurrentId() != PacketType.UNKNOWN_PACKET) {
idLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type);
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames()[0], type);
}
nameLookup.put(type.name(), type);
}
return this;
}
/**
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
* @param packetId - the legacy packet ID.
* @return The corresponding packet type, or NULL if not found.
*/
public PacketType getFromLegacy(int packetId) {
return legacyLookup.get(packetId);
}
/**
* Retrieve an unmodifiable view of all the packet types with this name.
* @param name - the name.
* @return The packet types, usually one.
*/
public Collection<PacketType> getFromName(String name) {
return Collections.unmodifiableCollection(nameLookup.get(name));
}
/**
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
* @param packetId - the legacy packet ID.
* @param preference - which packet type to look for first.
* @return The corresponding packet type, or NULL if not found.
*/
public PacketType getFromLegacy(int packetId, Sender preference) {
if (preference == Sender.CLIENT)
return getFirst(packetId, clientLookup, serverLookup);
else
return getFirst(packetId, serverLookup, clientLookup);
}
// Helper method for looking up in two sets
private <T> T getFirst(int packetId, IntegerMap<T> first, IntegerMap<T> second) {
if (first.containsKey(packetId))
return first.get(packetId);
else
return second.get(packetId);
}
/**
* Retrieve a packet type from a protocol, sender and packet ID.
* @param protocol - the current protocol.
* @param sender - the sender.
* @param packetId - the packet ID.
* @return The corresponding packet type, or NULL if not found.
* @deprecated IDs are no longer reliable
*/
@Deprecated
public PacketType getFromCurrent(Protocol protocol, Sender sender, int packetId) {
return idLookup.getMap(protocol, sender).get(packetId);
}
public PacketType getFromCurrent(Protocol protocol, Sender sender, String name) {
return classLookup.getMap(protocol, sender).get(name);
}
public ClassLookup getClassLookup() {
return classLookup;
}
}
@@ -0,0 +1,146 @@
package com.comphenix.protocol;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.compat.guava.Guava;
import com.comphenix.protocol.events.ConnectionSide;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
class PacketTypeParser {
public final static Range<Integer> DEFAULT_MAX_RANGE = Guava.closedRange(0, 255);
private Sender side = null;
private Protocol protocol = null;
public Set<PacketType> parseTypes(Deque<String> arguments, Range<Integer> defaultRange) {
Set<PacketType> result = Sets.newHashSet();
side = null;
protocol = null;
// Find these first
while (side == null) {
String arg = arguments.poll();
// Attempt to parse a side or protocol first
if (side == null) {
ConnectionSide connection = parseSide(arg);
if (connection != null) {
side = connection.getSender();
continue;
}
}
if (protocol == null) {
if ((protocol = parseProtocol(arg)) != null) {
continue;
}
}
throw new IllegalArgumentException("Specify connection side (CLIENT or SERVER).");
}
// Then we move on to parsing IDs (named packet types soon to come)
List<Range<Integer>> ranges = RangeParser.getRanges(arguments, DEFAULT_MAX_RANGE);
// And finally, parse packet names if we have a protocol
if (protocol != null) {
for (Iterator<String> it = arguments.iterator(); it.hasNext(); ) {
String name = it.next().toUpperCase();
Collection<PacketType> names = PacketType.fromName(name);
for (PacketType type : names) {
if (type.getProtocol() == protocol && type.getSender() == side) {
result.add(type);
it.remove();
}
}
}
}
// Supply a default integer range
if (ranges.isEmpty() && result.isEmpty()) {
ranges = Lists.newArrayList();
ranges.add(defaultRange);
}
for (Range<Integer> range : ranges) {
for (Integer id : Guava.toSet(range)) {
// Deprecated packets
if (protocol == null) {
if (PacketType.hasLegacy(id)) {
result.add(PacketType.findLegacy(id, side));
}
} else {
if (PacketType.hasCurrent(protocol, side, id)) {
result.add(PacketType.findCurrent(protocol, side, id));
}
}
}
}
return result;
}
/**
* Retrieve the last parsed protocol.
* @return Last protocol.
*/
public Protocol getLastProtocol() {
return protocol;
}
/**
* Retrieve the last sender.
* @return Last sender.
*/
public Sender getLastSide() {
return side;
}
/**
* Parse a connection sides from a string.
* @param text - the possible connection side.
* @return The connection side, or NULL if not found.
*/
public ConnectionSide parseSide(String text) {
if (text == null)
return null;
String candidate = text.toLowerCase();
// Parse the side gracefully
if ("client".startsWith(candidate))
return ConnectionSide.CLIENT_SIDE;
else if ("server".startsWith(candidate))
return ConnectionSide.SERVER_SIDE;
else
return null;
}
/**
* Parse a protocol from a string.
* @param text - the possible protocol.
* @return The protocol, or NULL if not found.
*/
public Protocol parseProtocol(String text) {
if (text == null)
return null;
String candidate = text.toLowerCase();
if ("handshake".equals(candidate) || "handshaking".equals(candidate))
return Protocol.HANDSHAKING;
else if ("login".equals(candidate))
return Protocol.LOGIN;
else if ("play".equals(candidate) || "game".equals(candidate))
return Protocol.PLAY;
else if ("status".equals(candidate))
return Protocol.STATUS;
else
return null;
}
}
@@ -25,15 +25,30 @@ import com.comphenix.protocol.reflect.IntEnum;
/**
* List of known packet IDs since 1.3.2.
*
* <p>
* Deprecated: Use {@link PacketType} instead.
* @author Kristian
*/
@Deprecated
public final class Packets {
/**
* The highest possible packet ID. It's unlikely that this value will ever change.
*/
public static final int MAXIMUM_PACKET_ID = 255;
/**
* The maximum number of unique packet IDs. It's unlikely this will ever change.
*/
public static final int PACKET_COUNT = 256;
/**
* List of packets sent only by the server.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @author Kristian
*/
@Deprecated
public static final class Server extends IntEnum {
/**
* The singleton instance. Can also be retrieved from the parent class.
@@ -52,9 +67,18 @@ public final class Packets {
public static final int PLAYER_POSITION = 11;
public static final int PLAYER_LOOK = 12;
public static final int PLAYER_LOOK_MOVE = 13;
/**
* Made bi-directional in 1.4.6.
*/
public static final int BLOCK_ITEM_SWITCH = 16;
public static final int ENTITY_LOCATION_ACTION = 17;
public static final int ARM_ANIMATION = 18;
public static final int NAMED_ENTITY_SPAWN = 20;
/**
* Removed in 1.4.6 and replaced with VEHICLE_SPAWN.
* @see <a href="http://www.wiki.vg/Protocol_History#2012-12-20">Protocol History - MinecraftCoalition</a>
*/
@Deprecated()
public static final int PICKUP_SPAWN = 21;
public static final int COLLECT = 22;
public static final int VEHICLE_SPAWN = 23;
@@ -71,10 +95,17 @@ public final class Packets {
public static final int ENTITY_HEAD_ROTATION = 35;
public static final int ENTITY_STATUS = 38;
public static final int ATTACH_ENTITY = 39;
/**
* Sent when an entities DataWatcher is updated.
* <p>
* Remember to clone the packet if you are modifying it.
*/
public static final int ENTITY_METADATA = 40;
public static final int MOB_EFFECT = 41;
public static final int REMOVE_MOB_EFFECT = 42;
public static final int SET_EXPERIENCE = 43;
public static final int UPDATE_ATTRIBUTES = 44;
public static final int MAP_CHUNK = 51;
public static final int MULTI_BLOCK_CHANGE = 52;
public static final int BLOCK_CHANGE = 53;
@@ -84,6 +115,7 @@ public final class Packets {
public static final int EXPLOSION = 60;
public static final int WORLD_EVENT = 61;
public static final int NAMED_SOUND_EFFECT = 62;
public static final int WORLD_PARTICLES = 63;
public static final int BED = 70;
public static final int WEATHER = 71;
public static final int OPEN_WINDOW = 100;
@@ -95,16 +127,37 @@ public final class Packets {
public static final int SET_CREATIVE_SLOT = 107;
public static final int UPDATE_SIGN = 130;
public static final int ITEM_DATA = 131;
/**
* Sent the first time a tile entity (chest inventory, etc.) is withing range of the player, or has been updated.
* <p>
* Remember to clone the packet if you are modifying it.
*/
public static final int TILE_ENTITY_DATA = 132;
public static final int OPEN_TILE_ENTITY = 133;
public static final int STATISTIC = 200;
public static final int PLAYER_INFO = 201;
public static final int ABILITIES = 202;
public static final int TAB_COMPLETE = 203;
public static final int SCOREBOARD_OBJECTIVE = 206;
public static final int UPDATE_SCORE = 207;
public static final int DISPLAY_SCOREBOARD = 208;
public static final int TEAMS = 209;
public static final int CUSTOM_PAYLOAD = 250;
public static final int KEY_RESPONSE = 252;
public static final int KEY_REQUEST = 253;
public static final int KICK_DISCONNECT = 255;
/**
* This packet was introduced in 1.7.2.
*/
public static final int PING_TIME = 230;
/**
* This packet was introduced in 1.7.2.
*/
public static final int LOGIN_SUCCESS = 232;
/**
* A registry that parses between names and packet IDs.
* @return The current server registry.
@@ -115,10 +168,13 @@ public final class Packets {
/**
* Determine if the given packet is a valid server packet in the current version of Minecraft.
* <p>
* Use {@link PacketType#isSupported()} instead.
* @param packetID - the packet to test.
* @return TRUE if this packet is supported, FALSE otherwise.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
@Deprecated
public static boolean isSupported(int packetID) throws FieldAccessException {
return PacketFilterManager.getServerPackets().contains(packetID);
}
@@ -128,6 +184,7 @@ public final class Packets {
* @return Every supported server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
@Deprecated
public static Set<Integer> getSupported() throws FieldAccessException {
return PacketFilterManager.getServerPackets();
}
@@ -140,8 +197,11 @@ public final class Packets {
/**
* List of packets sent by the client.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @author Kristian
*/
@Deprecated
public static class Client extends IntEnum {
/**
* The singleton instance. Can also be retrieved from the parent class.
@@ -153,7 +213,13 @@ public final class Packets {
public static final int HANDSHAKE = 2;
public static final int CHAT = 3;
public static final int USE_ENTITY = 7;
/**
* Since 1.3.1, the client no longer sends a respawn packet. Moved to CLIENT_COMMAND.
*/
@Deprecated
public static final int RESPAWN = 9;
public static final int FLYING = 10;
public static final int PLAYER_POSITION = 11;
public static final int PLAYER_LOOK = 12;
@@ -163,6 +229,7 @@ public final class Packets {
public static final int BLOCK_ITEM_SWITCH = 16;
public static final int ARM_ANIMATION = 18;
public static final int ENTITY_ACTION = 19;
public static final int PLAYER_INPUT = 27;
public static final int CLOSE_WINDOW = 101;
public static final int WINDOW_CLICK = 102;
public static final int TRANSACTION = 106;
@@ -178,6 +245,16 @@ public final class Packets {
public static final int GET_INFO = 254;
public static final int KICK_DISCONNECT = 255;
/**
* This packet was introduced in 1.7.2.
*/
public static final int PING_TIME = 230;
/**
* This packet was introduced in 1.7.2.
*/
public static final int LOGIN_START = 231;
/**
* A registry that parses between names and packet IDs.
* @return The current client registry.
@@ -213,25 +290,34 @@ public final class Packets {
/**
* A registry that parses between names and packet IDs.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @return The current client registry.
*/
@Deprecated
public static Server getServerRegistry() {
return Server.getRegistry();
}
/**
* A registry that parses between names and packet IDs.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @return The current server registry.
*/
@Deprecated
public static Client getClientRegistry() {
return Client.INSTANCE;
}
/**
* Find a packet by name. Must be capitalized and use underscores.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @param name - name of packet to find.
* @return The packet ID found.
*/
@Deprecated
public static int valueOf(String name) {
Integer serverAttempt = Server.INSTANCE.valueOf(name);
@@ -243,9 +329,12 @@ public final class Packets {
/**
* Retrieves the name of a packet.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @param packetID - packet to retrieve name.
* @return The name, or NULL if unable to find such a packet.
*/
@Deprecated
public static String getDeclaredName(int packetID) {
String serverAttempt = Server.INSTANCE.getDeclaredName(packetID);
@@ -0,0 +1,500 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
/**
* Represents the configuration of ProtocolLib.
*
* @author Kristian
*/
public class ProtocolConfig {
private static final String LAST_UPDATE_FILE = "lastupdate";
private static final String SECTION_GLOBAL = "global";
private static final String SECTION_AUTOUPDATER = "auto updater";
private static final String METRICS_ENABLED = "metrics";
private static final String IGNORE_VERSION_CHECK = "ignore version check";
private static final String BACKGROUND_COMPILER_ENABLED = "background compiler";
private static final String DEBUG_MODE_ENABLED = "debug";
private static final String DETAILED_ERROR = "detailed error";
private static final String INJECTION_METHOD = "injection method";
private static final String SCRIPT_ENGINE_NAME = "script engine";
private static final String SUPPRESSED_REPORTS = "suppressed reports";
private static final String UPDATER_NOTIFY = "notify";
private static final String UPDATER_DOWNLAD = "download";
private static final String UPDATER_DELAY = "delay";
// Defaults
private static final long DEFAULT_UPDATER_DELAY = 43200;
private Plugin plugin;
private Configuration config;
private boolean loadingSections;
private ConfigurationSection global;
private ConfigurationSection updater;
// Last update time
private long lastUpdateTime;
private boolean configChanged;
private boolean valuesChanged;
// Modifications
private int modCount;
public ProtocolConfig(Plugin plugin) {
this.plugin = plugin;
reloadConfig();
}
/**
* Reload configuration file.
*/
public void reloadConfig() {
// Reset
configChanged = false;
valuesChanged = false;
modCount++;
this.config = plugin.getConfig();
this.lastUpdateTime = loadLastUpdate();
loadSections(!loadingSections);
}
/**
* Load the last update time stamp from the file system.
*
* @return Last update time stamp.
*/
private long loadLastUpdate() {
File dataFile = getLastUpdateFile();
if (dataFile.exists()) {
try {
return Long.parseLong(Files.toString(dataFile, Charsets.UTF_8));
} catch (NumberFormatException e) {
plugin.getLogger().warning("Cannot parse " + dataFile + " as a number.");
} catch (IOException e) {
plugin.getLogger().warning("Cannot read " + dataFile);
}
}
// Default last update
return 0;
}
/**
* Store the given time stamp.
*
* @param value - time stamp to store.
*/
private void saveLastUpdate(long value) {
File dataFile = getLastUpdateFile();
// The data folder must exist
dataFile.getParentFile().mkdirs();
if (dataFile.exists())
dataFile.delete();
try {
Files.write(Long.toString(value), dataFile, Charsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException("Cannot write " + dataFile, e);
}
}
/**
* Retrieve the file that is used to store the update time stamp.
*
* @return File storing the update time stamp.
*/
private File getLastUpdateFile() {
return new File(plugin.getDataFolder(), LAST_UPDATE_FILE);
}
/**
* Load data sections.
*
* @param copyDefaults - whether or not to copy configuration defaults.
*/
private void loadSections(boolean copyDefaults) {
if (config != null) {
global = config.getConfigurationSection(SECTION_GLOBAL);
}
if (global != null) {
updater = global.getConfigurationSection(SECTION_AUTOUPDATER);
}
// Automatically copy defaults
if (copyDefaults && (!getFile().exists() || global == null || updater == null)) {
loadingSections = true;
if (config != null)
config.options().copyDefaults(true);
plugin.saveDefaultConfig();
plugin.reloadConfig();
loadingSections = false;
// Inform the user
plugin.getLogger().info("Created default configuration.");
}
}
/**
* Set a particular configuration key value pair.
*
* @param section - the configuration root.
* @param path - the path to the key.
* @param value - the value to set.
*/
private void setConfig(ConfigurationSection section, String path, Object value) {
configChanged = true;
section.set(path, value);
}
@SuppressWarnings("unchecked")
private <T> T getGlobalValue(String path, T def) {
try {
return (T) global.get(path, def);
} catch (Throwable ex) {
return def;
}
}
@SuppressWarnings("unchecked")
private <T> T getUpdaterValue(String path, T def) {
try {
return (T) updater.get(path, def);
} catch (Throwable ex) {
return def;
}
}
/**
* Retrieve a reference to the configuration file.
*
* @return Configuration file on disk.
*/
public File getFile() {
return new File(plugin.getDataFolder(), "config.yml");
}
/**
* Determine if detailed error reporting is enabled. Default FALSE.
*
* @return TRUE if it is enabled, FALSE otherwise.
*/
public boolean isDetailedErrorReporting() {
return getGlobalValue(DETAILED_ERROR, false);
}
/**
* Set whether or not detailed error reporting is enabled.
*
* @param value - TRUE if it is enabled, FALSE otherwise.
*/
public void setDetailedErrorReporting(boolean value) {
global.set(DETAILED_ERROR, value);
}
/**
* Retrieve whether or not ProtocolLib should determine if a new version has been released.
*
* @return TRUE if it should do this automatically, FALSE otherwise.
*/
public boolean isAutoNotify() {
return getUpdaterValue(UPDATER_NOTIFY, true);
}
/**
* Set whether or not ProtocolLib should determine if a new version has been released.
*
* @param value - TRUE to do this automatically, FALSE otherwise.
*/
public void setAutoNotify(boolean value) {
setConfig(updater, UPDATER_NOTIFY, value);
modCount++;
}
/**
* Retrieve whether or not ProtocolLib should automatically download the new version.
*
* @return TRUE if it should, FALSE otherwise.
*/
public boolean isAutoDownload() {
return updater != null && getUpdaterValue(UPDATER_DOWNLAD, true);
}
/**
* Set whether or not ProtocolLib should automatically download the new version.
*
* @param value - TRUE if it should. FALSE otherwise.
*/
public void setAutoDownload(boolean value) {
setConfig(updater, UPDATER_DOWNLAD, value);
modCount++;
}
/**
* Determine whether or not debug mode is enabled.
* <p>
* This grants access to the filter command.
*
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isDebug() {
return getGlobalValue(DEBUG_MODE_ENABLED, false);
}
/**
* Set whether or not debug mode is enabled.
*
* @param value - TRUE if it is enabled, FALSE otherwise.
*/
public void setDebug(boolean value) {
setConfig(global, DEBUG_MODE_ENABLED, value);
modCount++;
}
/**
* Retrieve an immutable list of every suppressed report type.
*
* @return Every suppressed report type.
*/
public ImmutableList<String> getSuppressedReports() {
return ImmutableList.copyOf(getGlobalValue(SUPPRESSED_REPORTS, new ArrayList<String>()));
}
/**
* Set the list of suppressed report types,
*
* @param reports - suppressed report types.
*/
public void setSuppressedReports(List<String> reports) {
global.set(SUPPRESSED_REPORTS, Lists.newArrayList(reports));
modCount++;
}
/**
* Retrieve the amount of time to wait until checking for a new update.
*
* @return The amount of time to wait.
*/
public long getAutoDelay() {
// Note that the delay must be greater than 59 seconds
return Math.max(getUpdaterValue(UPDATER_DELAY, 0), DEFAULT_UPDATER_DELAY);
}
/**
* Set the amount of time to wait until checking for a new update.
* <p>
* This time must be greater than 59 seconds.
*
* @param delaySeconds - the amount of time to wait.
*/
public void setAutoDelay(long delaySeconds) {
// Silently fix the delay
if (delaySeconds < DEFAULT_UPDATER_DELAY)
delaySeconds = DEFAULT_UPDATER_DELAY;
setConfig(updater, UPDATER_DELAY, delaySeconds);
modCount++;
}
/**
* The version of Minecraft to ignore the built-in safety feature.
*
* @return The version to ignore ProtocolLib's satefy.
*/
public String getIgnoreVersionCheck() {
return getGlobalValue(IGNORE_VERSION_CHECK, "");
}
/**
* Sets under which version of Minecraft the version safety feature will be ignored.
* <p>
* This is useful if a server operator has tested and verified that a version of ProtocolLib works, but doesn't want or can't upgrade to a newer version.
*
* @param ignoreVersion - the version of Minecraft where the satefy will be disabled.
*/
public void setIgnoreVersionCheck(String ignoreVersion) {
setConfig(global, IGNORE_VERSION_CHECK, ignoreVersion);
modCount++;
}
/**
* Retrieve whether or not metrics is enabled.
*
* @return TRUE if metrics is enabled, FALSE otherwise.
*/
public boolean isMetricsEnabled() {
return getGlobalValue(METRICS_ENABLED, true);
}
/**
* Set whether or not metrics is enabled.
* <p>
* This setting will take effect next time ProtocolLib is started.
*
* @param enabled - whether or not metrics is enabled.
*/
public void setMetricsEnabled(boolean enabled) {
setConfig(global, METRICS_ENABLED, enabled);
modCount++;
}
/**
* Retrieve whether or not the background compiler for structure modifiers is enabled or not.
*
* @return TRUE if it is enabled, FALSE otherwise.
*/
public boolean isBackgroundCompilerEnabled() {
return getGlobalValue(BACKGROUND_COMPILER_ENABLED, true);
}
/**
* Set whether or not the background compiler for structure modifiers is enabled or not.
* <p>
* This setting will take effect next time ProtocolLib is started.
*
* @param enabled - TRUE if is enabled/running, FALSE otherwise.
*/
public void setBackgroundCompilerEnabled(boolean enabled) {
setConfig(global, BACKGROUND_COMPILER_ENABLED, enabled);
modCount++;
}
/**
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
*
* @return Last update time.
*/
public long getAutoLastTime() {
return lastUpdateTime;
}
/**
* Set the last time we updated, in seconds since 1970.01.01 00:00.
* <p>
* Note that this is not considered to modify the configuration, so the modification count will not be incremented.
*
* @param lastTimeSeconds - new last update time.
*/
public void setAutoLastTime(long lastTimeSeconds) {
this.valuesChanged = true;
this.lastUpdateTime = lastTimeSeconds;
}
/**
* Retrieve the unique name of the script engine to use for filtering.
*
* @return Script engine to use.
*/
public String getScriptEngineName() {
return getGlobalValue(SCRIPT_ENGINE_NAME, "JavaScript");
}
/**
* Set the unique name of the script engine to use for filtering.
* <p>
* This setting will take effect next time ProtocolLib is started.
*
* @param name - name of the script engine to use.
*/
public void setScriptEngineName(String name) {
setConfig(global, SCRIPT_ENGINE_NAME, name);
modCount++;
}
/**
* Retrieve the default injection method.
*
* @return Default method.
*/
public PlayerInjectHooks getDefaultMethod() {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
/**
* Retrieve the injection method that has been set in the configuration, or use a default value.
*
* @return Injection method to use.
* @throws IllegalArgumentException If the configuration option is malformed.
*/
public PlayerInjectHooks getInjectionMethod() throws IllegalArgumentException {
String text = global.getString(INJECTION_METHOD);
// Default hook if nothing has been set
PlayerInjectHooks hook = getDefaultMethod();
if (text != null)
hook = PlayerInjectHooks.valueOf(text.toUpperCase().replace(" ", "_"));
return hook;
}
/**
* Set the starting injection method to use.
*
* @return Injection method.
*/
public void setInjectionMethod(PlayerInjectHooks hook) {
setConfig(global, INJECTION_METHOD, hook.name());
modCount++;
}
/**
* Retrieve the number of modifications made to this configuration.
*
* @return The number of modifications.
*/
public int getModificationCount() {
return modCount;
}
/**
* Save the current configuration file.
*/
public void saveAll() {
if (valuesChanged)
saveLastUpdate(lastUpdateTime);
if (configChanged)
plugin.saveConfig();
// And we're done
valuesChanged = false;
configChanged = false;
}
}
@@ -1,197 +1,707 @@
/*
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.executors.BukkitExecutors;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.MonitorAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.DelegatedErrorReporter;
import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.DelayedSingleTask;
import com.comphenix.protocol.injector.InternalManager;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
/**
* The main entry point for ProtocolLib.
*
*
* @author Kristian
*/
public class ProtocolLibrary extends JavaPlugin {
// Every possible error or warning report type
public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration");
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType("Cannot delete old ProtocolLib configuration.");
public static final ReportType REPORT_CANNOT_PARSE_INJECTION_METHOD = new ReportType("Cannot parse injection method. Using default.");
public static final ReportType REPORT_PLUGIN_LOAD_ERROR = new ReportType("Cannot load ProtocolLib.");
public static final ReportType REPORT_PLUGIN_ENABLE_ERROR = new ReportType("Cannot enable ProtocolLib.");
public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType("Unable to retrieve current Minecraft version. Assuming %s");
public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType("Unable to detect conflicting plugin versions.");
public static final ReportType REPORT_CANNOT_REGISTER_COMMAND = new ReportType("Cannot register command %s: %s");
public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType("Unable to create packet timeout task.");
public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot perform automatic updates.");
/**
* The minimum version ProtocolLib has been tested with.
*/
public static final String MINIMUM_MINECRAFT_VERSION = "1.0";
/**
* The maximum version ProtocolLib has been tested with,
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "1.8.8";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.8.8) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2015-07-27";
// Update information
static final String BUKKIT_DEV_SLUG = "protocollib";
static final int BUKKIT_DEV_ID = 45564;
// Different commands
private enum ProtocolCommand {
FILTER,
PACKET,
PROTOCOL
}
/**
* The number of milliseconds per second.
*/
static final long MILLI_PER_SECOND = 1000;
private static final String PERMISSION_INFO = "protocol.info";
// There should only be one protocol manager, so we'll make it static
private static PacketFilterManager protocolManager;
// Error logger
private Logger logger;
// Metrics and statistisc
private Statistics statistisc;
private static InternalManager protocolManager;
// Error reporter
private static ErrorReporter reporter = new BasicErrorReporter();
// Strongly typed configuration
private static ProtocolConfig config;
// Metrics and statistics
private Statistics statistics;
// Executors
private static ListeningScheduledExecutorService executorAsync;
private static ListeningScheduledExecutorService executorSync;
// Structure compiler
private BackgroundCompiler backgroundCompiler;
// Used to clean up server packets that have expired.
// But mostly required to simulate recieving client packets.
private int asyncPacketTask = -1;
// Used to clean up server packets that have expired, but mostly required to simulate
// recieving client packets.
private int packetTask = -1;
private int tickCounter = 0;
private static final int ASYNC_PACKET_DELAY = 1;
private static final int ASYNC_MANAGER_DELAY = 1;
// Used to unhook players after a delay
private DelayedSingleTask unhookTask;
// Used for debugging
private boolean debugListener;
// Settings/options
private int configExpectedMod = -1;
// Updater
private Updater updater;
private static boolean UPDATES_DISABLED;
// Logger
private static Logger logger;
private Handler redirectHandler;
// Commands
private CommandProtocol commandProtocol;
private CommandPacket commandPacket;
private CommandFilter commandFilter;
// Whether or not disable is not needed
private boolean skipDisable;
@Override
public void onLoad() {
// Load configuration
logger = getLoggerSafely();
unhookTask = new DelayedSingleTask(this);
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, logger);
}
@Override
public void onEnable() {
Server server = getServer();
PluginManager manager = server.getPluginManager();
// Initialize background compiler
if (backgroundCompiler == null) {
backgroundCompiler = new BackgroundCompiler(getClassLoader());
BackgroundCompiler.setInstance(backgroundCompiler);
Application.registerPrimaryThread();
// Initialize enhancer factory
EnhancerFactory.getInstance().setClassLoader(getClassLoader());
// Initialize executors
executorAsync = BukkitExecutors.newAsynchronous(this);
executorSync = BukkitExecutors.newSynchronous(this);
// Add global parameters
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
reporter = getFilteredReporter(detailedReporter);
// Configuration
saveDefaultConfig();
reloadConfig();
try {
config = new ProtocolConfig(this);
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
// Load it again
if (deleteConfig()) {
config = new ProtocolConfig(this);
} else {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DELETE_CONFIG));
}
}
// Print the state of the debug mode
if (config.isDebug()) {
logger.warning("Debug mode is enabled!");
}
// And the state of the error reporter
if (config.isDetailedErrorReporting()) {
detailedReporter.setDetailedReporting(true);
logger.warning("Detailed error reporting enabled!");
}
// Notify server managers of incompatible plugins
checkForIncompatibility(manager);
// Player login and logout events
protocolManager.registerEvents(manager, this);
// Worker that ensures that async packets are eventually sent
createAsyncTask(server);
//toggleDebugListener();
// Try to enable statistics
try {
statistisc = new Statistics(this);
} catch (IOException e) {
logger.log(Level.SEVERE, "Unable to enable metrics.", e);
// Check for other versions
checkConflictingVersions();
// Handle unexpected Minecraft versions
MinecraftVersion version = verifyMinecraftVersion();
// Set updater - this will not perform any update automatically
updater = Updater.create(this, BUKKIT_DEV_ID, getFile(), UpdateType.NO_DOWNLOAD, true);
unhookTask = new DelayedSingleTask(this);
protocolManager = PacketFilterManager.newBuilder()
.classLoader(getClassLoader())
.server(getServer())
.library(this)
.minecraftVersion(version)
.unhookTask(unhookTask)
.reporter(reporter)
.build();
// Setup error reporter
detailedReporter.addGlobalParameter("manager", protocolManager);
// Update injection hook
try {
PlayerInjectHooks hook = config.getInjectionMethod();
// Only update the hook if it's different
if (!protocolManager.getPlayerHook().equals(hook)) {
logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook);
protocolManager.setPlayerHook(hook);
}
} catch (IllegalArgumentException e) {
reporter.reportWarning(config, Report.newBuilder(REPORT_CANNOT_PARSE_INJECTION_METHOD).error(e));
}
// Send logging information to player listeners too
initializeCommands();
setupBroadcastUsers(PERMISSION_INFO);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
logger.log(Level.SEVERE, "Metrics cannot be enabled. Incompatible Bukkit version.", e);
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
disablePlugin();
}
}
/**
* Toggle a listener that prints every sent and received packet.
* Initialize all command handlers.
*/
void toggleDebugListener() {
if (debugListener) {
protocolManager.removePacketListeners(this);
} else {
// DEBUG DEBUG
protocolManager.addPacketListener(new MonitorAdapter(this, ConnectionSide.BOTH, logger) {
@Override
public void onPacketReceiving(PacketEvent event) {
Object handle = event.getPacket().getHandle();
System.out.println(String.format(
"RECEIVING %s@%s from %s.",
handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName()
));
};
@Override
public void onPacketSending(PacketEvent event) {
Object handle = event.getPacket().getHandle();
System.out.println(String.format(
"SENDING %s@%s from %s.",
handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName()
));
private void initializeCommands() {
// Initialize command handlers
for (ProtocolCommand command : ProtocolCommand.values()) {
try {
switch (command) {
case PROTOCOL:
commandProtocol = new CommandProtocol(reporter, this, updater, config);
break;
case FILTER:
commandFilter = new CommandFilter(reporter, this, config);
break;
case PACKET:
commandPacket = new CommandPacket(reporter, this, logger, commandFilter, protocolManager);
break;
}
});
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (LinkageError e) {
logger.warning("Failed to register command " + command.name() + ": " + e);
} catch (Throwable e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND)
.messageParam(command.name(), e.getMessage()).error(e));
}
}
debugListener = !debugListener;
}
private void createAsyncTask(Server server) {
/**
* Retrieve a error reporter that may be filtered by the configuration.
* @return The new default error reporter.
*/
private ErrorReporter getFilteredReporter(ErrorReporter reporter) {
return new DelegatedErrorReporter(reporter) {
private int lastModCount = -1;
private Set<String> reports = Sets.newHashSet();
@Override
protected Report filterReport(Object sender, Report report, boolean detailed) {
try {
String canonicalName = ReportType.getReportName(sender, report.getType());
String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase();
if (config != null && config.getModificationCount() != lastModCount) {
// Update our cached set again
reports = Sets.newHashSet(config.getSuppressedReports());
lastModCount = config.getModificationCount();
}
// Cancel reports either on the full canonical name, or just the report name
if (reports.contains(canonicalName) || reports.contains(reportName))
return null;
} catch (Exception e) {
// Only report this with a minor message
logger.warning("Error filtering reports: " + e.toString());
}
// Don't filter anything
return report;
}
};
}
private boolean deleteConfig() {
return config.getFile().delete();
}
@Override
public void reloadConfig() {
super.reloadConfig();
// Reload configuration
if (config != null) {
config.reloadConfig();
}
}
private void setupBroadcastUsers(final String permission) {
// Guard against multiple calls
if (redirectHandler != null)
return;
// Broadcast information to every user too
redirectHandler = new Handler() {
@Override
public void publish(LogRecord record) {
// Only display warnings and above
if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
commandPacket.broadcastMessageSilently(record.getMessage(), permission);
}
}
@Override
public void flush() {
// Not needed.
}
@Override
public void close() throws SecurityException {
// Do nothing.
}
};
logger.addHandler(redirectHandler);
}
@Override
public void onEnable() {
try {
if (asyncPacketTask >= 0)
throw new IllegalStateException("Async task has already been created");
Server server = getServer();
PluginManager manager = server.getPluginManager();
// Don't do anything else!
if (manager == null)
return;
// Silly plugin reloaders!
if (protocolManager == null) {
Logger directLogging = Logger.getLogger("Minecraft");
String[] message = new String[] {
" ProtocolLib does not support plugin reloaders! ", " Please use the built-in reload command! "
};
// Print as severe
for (String line : ChatExtensions.toFlowerBox(message, "*", 3, 1)) {
directLogging.severe(line);
}
disablePlugin();
return;
}
// Check for incompatible plugins
checkForIncompatibility(manager);
// Initialize background compiler
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);
BackgroundCompiler.setInstance(backgroundCompiler);
logger.info("Started structure compiler thread.");
} else {
logger.info("Structure compiler thread has been disabled.");
}
// Set up command handlers
registerCommand(CommandProtocol.NAME, commandProtocol);
registerCommand(CommandPacket.NAME, commandPacket);
registerCommand(CommandFilter.NAME, commandFilter);
// Player login and logout events
protocolManager.registerEvents(manager, this);
// Worker that ensures that async packets are eventually sent
// It also performs the update check.
createPacketTask(server);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e));
disablePlugin();
return;
}
// Try to enable statistics
try {
if (config.isMetricsEnabled()) {
statistics = new Statistics(this);
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (IOException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(statistics));
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam(statistics));
}
}
// Plugin authors: Notify me to remove these
public static List<String> INCOMPATIBLE = Arrays.asList("TagAPI");
private void checkForIncompatibility(PluginManager manager) {
for (String plugin : INCOMPATIBLE) {
if (manager.getPlugin(plugin) != null) {
// Special case for TagAPI and iTag
if (plugin.equals("TagAPI")) {
Plugin iTag = manager.getPlugin("iTag");
if (iTag == null || iTag.getDescription().getVersion().startsWith("1.0")) {
logger.severe("Detected incompatible plugin: TagAPI");
}
} else {
logger.severe("Detected incompatible plugin: " + plugin);
}
}
}
}
// Used to check Minecraft version
private MinecraftVersion verifyMinecraftVersion() {
MinecraftVersion minimum = new MinecraftVersion(MINIMUM_MINECRAFT_VERSION);
MinecraftVersion maximum = new MinecraftVersion(MAXIMUM_MINECRAFT_VERSION);
try {
MinecraftVersion current = new MinecraftVersion(getServer());
// Skip certain versions
if (!config.getIgnoreVersionCheck().equals(current.getVersion())) {
// We'll just warn the user for now
if (current.compareTo(minimum) < 0)
logger.warning("Version " + current + " is lower than the minimum " + minimum);
if (current.compareTo(maximum) > 0)
logger.warning("Version " + current + " has not yet been tested! Proceed with caution.");
}
return current;
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e).messageParam(maximum));
// Unknown version - just assume it is the latest
return maximum;
}
}
private void checkConflictingVersions() {
Pattern ourPlugin = Pattern.compile("ProtocolLib-(.*)\\.jar");
MinecraftVersion currentVersion = new MinecraftVersion(getDescription().getVersion());
MinecraftVersion newestVersion = null;
// Skip the file that contains this current instance however
File loadedFile = getFile();
try {
// Scan the plugin folder for newer versions of ProtocolLib
// The plugin folder isn't always plugins/
File pluginFolder = getDataFolder().getParentFile();
File[] candidates = pluginFolder.listFiles();
if (candidates != null) {
for (File candidate : pluginFolder.listFiles()) {
if (candidate.isFile() && !candidate.equals(loadedFile)) {
Matcher match = ourPlugin.matcher(candidate.getName());
if (match.matches()) {
MinecraftVersion version = new MinecraftVersion(match.group(1));
if (candidate.length() == 0) {
// Delete and inform the user
logger.info((candidate.delete() ? "Deleted " : "Could not delete ") + candidate);
} else if (newestVersion == null || newestVersion.compareTo(version) < 0) {
newestVersion = version;
}
}
}
}
}
} catch (Exception e) {
// TODO This shows [ProtocolLib] and [ProtocolLibrary] in the message
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS).error(e));
}
// See if the newest version is actually higher
if (newestVersion != null && currentVersion.compareTo(newestVersion) < 0) {
// We don't need to set internal classes or instances to NULL - that would break the other loaded plugin
skipDisable = true;
throw new IllegalStateException(String.format(
"Detected a newer version of ProtocolLib (%s) in plugin folder than the current (%s). Disabling.",
newestVersion.getVersion(), currentVersion.getVersion()));
}
}
private void registerCommand(String name, CommandExecutor executor) {
try {
// Ignore these - they must have printed an error already
if (executor == null)
return;
PluginCommand command = getCommand(name);
// Try to load the command
if (command != null) {
command.setExecutor(executor);
} else {
throw new RuntimeException("plugin.yml might be corrupt.");
}
} catch (RuntimeException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
}
}
/**
* Disable the current plugin.
*/
private void disablePlugin() {
getServer().getPluginManager().disablePlugin(this);
}
private void createPacketTask(Server server) {
try {
if (packetTask >= 0)
throw new IllegalStateException("Packet task has already been created");
// Attempt to create task
asyncPacketTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
packetTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
@Override
public void run() {
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
// We KNOW we're on the main thread at the moment
manager.sendProcessedPackets(tickCounter++, true);
// House keeping
updateConfiguration();
// Check for updates too
if (!UPDATES_DISABLED && (tickCounter % 20) == 0) {
checkUpdates();
}
}
}, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY);
}, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
if (asyncPacketTask == -1) {
logger.log(Level.SEVERE, "Unable to create packet timeout task.", e);
if (packetTask == -1) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CREATE_TIMEOUT_TASK).error(e));
}
}
}
private void checkForIncompatibility(PluginManager manager) {
// Plugin authors: Notify me to remove these
String[] incompatiblePlugins = {};
private void updateConfiguration() {
if (config != null && config.getModificationCount() != configExpectedMod) {
configExpectedMod = config.getModificationCount();
// Update the debug flag
protocolManager.setDebug(config.isDebug());
}
}
private void checkUpdates() {
// Ignore milliseconds - it's pointless
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
for (String plugin : incompatiblePlugins) {
if (manager.getPlugin(plugin) != null) {
// Check for versions, ect.
logger.severe("Detected incompatible plugin: " + plugin);
try {
long updateTime = config.getAutoLastTime() + config.getAutoDelay();
// Should we update?
if (currentTime > updateTime && !updater.isChecking()) {
// Initiate the update as if it came from the console
if (config.isAutoDownload())
commandProtocol.updateVersion(getServer().getConsoleSender(), false);
else if (config.isAutoNotify())
commandProtocol.checkVersion(getServer().getConsoleSender(), false);
else
commandProtocol.updateFinished();
}
} catch (Exception e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
UPDATES_DISABLED = true;
}
}
@Override
public void onDisable() {
if (skipDisable) {
return;
}
// Disable compiler
if (backgroundCompiler != null) {
backgroundCompiler.shutdownAll();
backgroundCompiler = null;
BackgroundCompiler.setInstance(null);
}
// Clean up
if (asyncPacketTask >= 0) {
getServer().getScheduler().cancelTask(asyncPacketTask);
asyncPacketTask = -1;
if (packetTask >= 0) {
getServer().getScheduler().cancelTask(packetTask);
packetTask = -1;
}
unhookTask.close();
protocolManager.close();
// And redirect handler too
if (redirectHandler != null) {
logger.removeHandler(redirectHandler);
}
if (protocolManager != null)
protocolManager.close();
else
return; // Plugin reloaders!
if (unhookTask != null)
unhookTask.close();
protocolManager = null;
statistisc = null;
statistics = null;
// To clean up global parameters
reporter = new BasicErrorReporter();
}
// Get the Bukkit logger first, before we try to create our own
private Logger getLoggerSafely() {
Logger log = null;
try {
log = getLogger();
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Ignore
}
// Use the default logger instead
if (log == null)
log = Logger.getLogger("Minecraft");
return log;
}
/**
* Retrieve the current error reporter.
* <p>
* This is guaranteed to not be NULL in 2.5.0 and later.
* @return Current error reporter.
*/
public static ErrorReporter getErrorReporter() {
return reporter;
}
/**
* Retrieve the current strongly typed configuration.
* @return The configuration, or NULL if ProtocolLib hasn't loaded yet.
*/
public static ProtocolConfig getConfiguration() {
return config;
}
/**
* Retrieves the packet protocol manager.
* @return Packet protocol manager, or NULL if it has been disabled.
@@ -199,30 +709,53 @@ public class ProtocolLibrary extends JavaPlugin {
public static ProtocolManager getProtocolManager() {
return protocolManager;
}
/**
* Retrieve the metrics instance used to measure users of this library.
* <p>
* Note that this method may return NULL when the server is reloading or shutting down.
* Note that this method may return NULL when the server is reloading or shutting down. It is also
* NULL if metrics has been disabled.
* @return Metrics instance container.
*/
public Statistics getStatistics() {
return statistisc;
return statistics;
}
// Get the Bukkit logger first, before we try to create our own
private Logger getLoggerSafely() {
Logger log = null;
try {
log = getLogger();
} catch (Throwable e) {
// We'll handle it
}
if (log == null)
log = Logger.getLogger("Minecraft");
return log;
/**
* Retrieve an executor service for performing asynchronous tasks on the behalf of ProtocolLib.
* <p>
* Note that this service is NULL if ProtocolLib has not been initialized yet.
* @return The executor service, or NULL.
*/
public static ListeningScheduledExecutorService getExecutorAsync() {
return executorAsync;
}
/**
* Retrieve an executor service for performing synchronous tasks (main thread) on the behalf of ProtocolLib.
* <p>
* Note that this service is NULL if ProtocolLib has not been initialized yet.
* @return The executor service, or NULL.
*/
public static ListeningScheduledExecutorService getExecutorSync() {
return executorSync;
}
// ---- Logging Methods
public static void log(Level level, String message, Object... args) {
logger.log(level, MessageFormat.format(message, args));
}
public static void log(String message, Object... args) {
log(Level.INFO, message, args);
}
public static void log(Level level, String message, Throwable ex) {
logger.log(level, message, ex);
}
public void disableUpdates() {
UPDATES_DISABLED = true;
}
}
@@ -2,32 +2,38 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Set;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.ImmutableSet;
/**
@@ -35,7 +41,73 @@ import com.google.common.collect.ImmutableSet;
* @author Kristian
*/
public interface ProtocolManager extends PacketStream {
/**
* Retrieve the protocol version of a given player.
* <p>
* This only really makes sense of a server that support clients of multiple Minecraft versions, such as Spigot #1628.
* @param player - the player.
* @return The associated protocol version, or {@link Integer#MIN_VALUE} if unknown.
*/
public int getProtocolVersion(Player player);
/**
* Send a packet to the given player.
* <p>
* Re-sending a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()}
* to delay a packet until a certain condition has been met.
*
* @param receiver - the receiver.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occurred when sending the packet.
*/
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
throws InvocationTargetException;
/**
* Simulate receiving a certain packet from a given player.
* <p>
* Receiving a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()}
* to delay a packet until a certain condition has been met.
*
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
throws IllegalAccessException, InvocationTargetException;
/**
* Broadcast a given packet to every connected player on the server.
* @param packet - the packet to broadcast.
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
*/
public void broadcastServerPacket(PacketContainer packet);
/**
* Broadcast a packet to every player that is receiving information about a given entity.
* <p>
* This is usually every player in the same world within an observable distance. If the entity is a
* player, it will only be included if <i>includeTracker</i> is TRUE.
* @param packet - the packet to broadcast.
* @param entity - the entity whose trackers we will inform.
* @param includeTracker - whether or not to also transmit the packet to the entity, if it is a tracker.
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
*/
public void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker);
/**
* Broadcast a packet to every player within the given maximum observer distance.
* @param packet - the packet to broadcast.
* @param origin - the origin to consider when calculating the distance to each observer.
* @param maxObserverDistance - the maximum distance to the origin.
*/
public void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance);
/**
* Retrieves a list of every registered packet listener.
* @return Every registered packet listener.
@@ -43,17 +115,17 @@ public interface ProtocolManager extends PacketStream {
public ImmutableSet<PacketListener> getPacketListeners();
/**
* Adds a packet listener.
* Adds a packet listener.
* <p>
* Adding an already registered listener has no effect. If you need to change the packets
* the current listener is observing, you must first remove the packet listener before you
* Adding an already registered listener has no effect. If you need to change the packets
* the current listener is observing, you must first remove the packet listener before you
* can register it again.
* @param listener - new packet listener.
*/
public void addPacketListener(PacketListener listener);
/**
* Removes a given packet listener.
* Removes a given packet listener.
* <p>
* Attempting to remove a listener that doesn't exist has no effect.
* @param listener - the packet listener to remove.
@@ -68,64 +140,143 @@ public interface ProtocolManager extends PacketStream {
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* <p>
* Deprecated: Use {@link #createPacket(PacketType)} instead.
* @param id - packet ID.
* @return New encapsulated Minecraft packet.
*/
@Deprecated
public PacketContainer createPacket(int id);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* @param type - packet type.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(PacketType type);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* <p>
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
* give non-primitive fields in the packet sensible default values. For instance, certain
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
* forceDefaults option is true, the List or Set will be automatically created.
* <p>
* Deprecated: Use {@link #createPacket(PacketType, boolean)} instead.
*
* @param id - packet ID.
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
* @return New encapsulated Minecraft packet.
*/
@Deprecated
public PacketContainer createPacket(int id, boolean forceDefaults);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* <p>
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
* give non-primitive fields in the packet sensible default values. For instance, certain
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
* forceDefaults option is true, the List or Set will be automatically created.
*
* @param type - packet type.
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(PacketType type, boolean forceDefaults);
/**
* Construct a packet using the special builtin Minecraft constructors.
* <p>
* Deprecated: Use {@link #createPacketConstructor(PacketType, Object...)} instead.
* @param id - the packet ID.
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
@Deprecated
public PacketConstructor createPacketConstructor(int id, Object... arguments);
/**
* Completely refresh all clients about an entity.
* Construct a packet using the special builtin Minecraft constructors.
* @param type - the packet type.
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
/**
* Completely resend an entity to a list of clients.
* <p>
* Note that this method is NOT thread safe. If you call this method from anything
* Note that this method is NOT thread safe. If you call this method from anything
* but the main thread, it will throw an exception.
* @param entity - entity to refresh.
* @param observers - the clients to update.
*/
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
/**
* Retrieve the associated entity.
* @param container - the world the entity belongs to.
* @param id - the unique ID of the entity.
* @return The associated entity.
* @throws FieldAccessException Reflection failed.
*/
public Entity getEntityFromID(World container, int id) throws FieldAccessException;
/**
* Retrieve every client that is receiving information about a given entity.
* @param entity - the entity that is being tracked.
* @return Every client/player that is tracking the given entity.
* @throws FieldAccessException If reflection failed.
*/
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException;
/**
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
* <p>
* Deprecated: Use {@link #getSendingFilterTypes()} instead.
* @return Every filtered server packet.
*/
@Deprecated
public Set<Integer> getSendingFilters();
/**
* Retrieves a immutable set containing the ID of the recieved client packets that will be observed by listeners.
* Retrieves a immutable set containing the type of the sent server packets that will be observed by listeners.
* @return Every filtered server packet.
*/
public Set<PacketType> getSendingFilterTypes();
/**
* Retrieves a immutable set containing the ID of the received client packets that will be observed by listeners.
* <p>
* Deprecated: Use {@link #getReceivingFilterTypes()} instead.
* @return Every filtered client packet.
*/
@Deprecated
public Set<Integer> getReceivingFilters();
/**
* Determines whether or not this protocol mananger has been disabled.
* Retrieves a immutable set containing the type of the received client packets that will be observed by listeners.
* @return Every filtered client packet.
*/
public Set<PacketType> getReceivingFilterTypes();
/**
* Retrieve the current Minecraft version.
* @return The current version.
*/
public MinecraftVersion getMinecraftVersion();
/**
* Determines whether or not this protocol manager has been disabled.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClosed();
/**
* Retrieve the current asyncronous packet manager.
* @return Asyncronous packet manager.
* Retrieve the current asynchronous packet manager.
* @return Asynchronous packet manager.
*/
public AsynchronousManager getAsynchronousManager();
}
@@ -0,0 +1,163 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import com.comphenix.protocol.compat.guava.Guava;
import com.google.common.collect.Range;
/**
* Used to parse ranges in CommandPacket.
*
* @author Kristian
*/
class RangeParser {
/**
* Parse a range from a given text.
* @param text - the text.
* @param legalRange - range of legal values.
* @return The parsed ranges.
*/
public static List<Range<Integer>> getRanges(String text, Range<Integer> legalRange) {
return getRanges(new ArrayDeque<String>(Arrays.asList(text)), legalRange);
}
/**
* Parse ranges from an array of elements.
* @param args - array of elements.
* @param offset - beginning offset.
* @param lastIndex - the last index of the array to read.
* @param legalRange - range of legal values.
* @return The parsed ranges.
*/
public static List<Range<Integer>> getRanges(Deque<String> input, Range<Integer> legalRange) {
List<String> tokens = tokenizeInput(input);
List<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
for (int i = 0; i < tokens.size(); i++) {
Range<Integer> range;
String current = tokens.get(i);
String next = i + 1 < tokens.size() ? tokens.get(i + 1) : null;
// Yoda equality is done for null-safety
if ("-".equals(current)) {
throw new IllegalArgumentException("A hyphen must appear between two numbers.");
} else if ("-".equals(next)) {
if (i + 2 >= tokens.size())
throw new IllegalArgumentException("Cannot form a range without a upper limit.");
// This is a proper range
range = Guava.closedRange(Integer.parseInt(current), Integer.parseInt(tokens.get(i + 2)));
ranges.add(range);
// Skip the two next tokens
i += 2;
} else {
// Just a single number
range = Guava.singleton(Integer.parseInt(current));
ranges.add(range);
}
// Validate ranges
if (!legalRange.encloses(range)) {
throw new IllegalArgumentException(range + " is not in the range " + range.toString());
}
}
return simplify(ranges, legalRange.upperEndpoint());
}
/**
* Simplify a list of ranges by assuming a maximum value.
* @param ranges - the list of ranges to simplify.
* @param maximum - the maximum value (minimum value is always 0).
* @return A simplified list of ranges.
*/
private static List<Range<Integer>> simplify(List<Range<Integer>> ranges, int maximum) {
List<Range<Integer>> result = new ArrayList<Range<Integer>>();
boolean[] set = new boolean[maximum + 1];
int start = -1;
// Set every ID
for (Range<Integer> range : ranges) {
for (int id : Guava.toSet(range)) {
set[id] = true;
}
}
// Generate ranges from this set
for (int i = 0; i <= set.length; i++) {
if (i < set.length && set[i]) {
if (start < 0) {
start = i;
}
} else {
if (start >= 0) {
result.add(Guava.closedRange(start, i - 1));
start = -1;
}
}
}
return result;
}
private static List<String> tokenizeInput(Deque<String> input) {
List<String> tokens = new ArrayList<String>();
// Tokenize the input
while (!input.isEmpty()) {
StringBuilder number = new StringBuilder();
String text = input.peek();
for (int j = 0; j < text.length(); j++) {
char current = text.charAt(j);
if (Character.isDigit(current)) {
number.append(current);
} else if (Character.isWhitespace(current)) {
// That's ok
} else if (current == '-') {
// Add the number token first
if (number.length() > 0) {
tokens.add(number.toString());
number.setLength(0);
}
tokens.add(Character.toString(current));
} else {
// We're no longer dealing with integers - quit
return tokens;
}
}
// Add the number token, if it hasn't already
if (number.length() > 0)
tokens.add(number.toString());
input.poll();
}
return tokens;
}
}
@@ -2,16 +2,16 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
@@ -20,86 +20,154 @@ package com.comphenix.protocol.async;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.comphenix.protocol.injector.SortedPacketListenerList;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
/**
* Represents a filter manager for asynchronous packets.
*
* <p>
* By using {@link AsyncMarker#incrementProcessingDelay()}, a packet can be delayed without having to block the
* processing thread.
* @author Kristian
*/
public class AsyncFilterManager implements AsynchronousManager {
private SortedPacketListenerList serverTimeoutListeners;
private SortedPacketListenerList clientTimeoutListeners;
private Set<PacketListener> timeoutListeners;
private PacketProcessingQueue serverProcessingQueue;
private PacketSendingQueue serverQueue;
private PacketProcessingQueue clientProcessingQueue;
private PacketSendingQueue clientQueue;
// Sending queues
private final PlayerSendingHandler playerSendingHandler;
private Logger logger;
// Report exceptions
private final ErrorReporter reporter;
// The likely main thread
private Thread mainThread;
private final Thread mainThread;
// Default scheduler
private BukkitScheduler scheduler;
private final BukkitScheduler scheduler;
// Current packet index
private final AtomicInteger currentSendingIndex = new AtomicInteger();
// Our protocol manager
private ProtocolManager manager;
// Current packet index
private AtomicInteger currentSendingIndex = new AtomicInteger();
// Whether or not we're currently cleaning up
private volatile boolean cleaningUp;
public AsyncFilterManager(Logger logger, BukkitScheduler scheduler, ProtocolManager manager) {
// Server packets are synchronized already
this.serverQueue = new PacketSendingQueue(false);
// Client packets must be synchronized
this.clientQueue = new PacketSendingQueue(true);
this.serverProcessingQueue = new PacketProcessingQueue(serverQueue);
this.clientProcessingQueue = new PacketProcessingQueue(clientQueue);
/**
* Initialize a asynchronous filter manager.
* <p>
* <b>Internal method</b>. Retrieve the global asynchronous manager from the protocol manager instead.
* @param reporter - desired error reporter.
* @param scheduler - task scheduler.
*/
public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler) {
// Initialize timeout listeners
this.serverTimeoutListeners = new SortedPacketListenerList();
this.clientTimeoutListeners = new SortedPacketListenerList();
this.timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners);
this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
this.clientProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
this.playerSendingHandler.initializeScheduler();
this.scheduler = scheduler;
this.manager = manager;
this.logger = logger;
this.reporter = reporter;
this.mainThread = Thread.currentThread();
}
/**
* Retrieve the protocol manager.
* @return The protocol manager.
*/
public ProtocolManager getManager() {
return manager;
}
/**
* Set the associated protocol manager.
* @param manager - the new manager.
*/
public void setManager(ProtocolManager manager) {
this.manager = manager;
}
@Override
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
return registerAsyncHandler(listener, true);
}
@Override
public void registerTimeoutHandler(PacketListener listener) {
if (listener == null)
throw new IllegalArgumentException("listener cannot be NULL.");
if (!timeoutListeners.add(listener))
return;
ListeningWhitelist sending = listener.getSendingWhitelist();
ListeningWhitelist receiving = listener.getReceivingWhitelist();
if (!ListeningWhitelist.isEmpty(sending))
serverTimeoutListeners.addListener(listener, sending);
if (!ListeningWhitelist.isEmpty(receiving))
serverTimeoutListeners.addListener(listener, receiving);
}
@Override
public Set<PacketListener> getTimeoutHandlers() {
return ImmutableSet.copyOf(timeoutListeners);
}
@Override
public Set<PacketListener> getAsyncHandlers() {
ImmutableSet.Builder<PacketListener> builder = ImmutableSet.builder();
// Add every asynchronous packet listener
for (PrioritizedListener<AsyncListenerHandler> handler :
Iterables.concat(serverProcessingQueue.values(), clientProcessingQueue.values())) {
builder.add(handler.getListener().getAsyncListener());
}
return builder.build();
}
/**
* Registers an asynchronous packet handler.
* <p>
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted.
* <p>
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
* <p>
* Asynchronous events will only be executed if a synchronous listener with the same packets is registered.
* If you already have a synchronous event, call this method with autoInject set to FALSE.
*
* @param listener - the packet listener that will recieve these asynchronous events.
* @param listener - the packet listener that will receive these asynchronous events.
* @param autoInject - whether or not to automatically create the corresponding synchronous listener,
* @return An asynchrouns handler.
* @return An asynchronous handler.
*/
public AsyncListenerHandler registerAsyncHandler(PacketListener listener, boolean autoInject) {
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
@@ -107,12 +175,15 @@ public class AsyncFilterManager implements AsynchronousManager {
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist();
if (!hasValidWhitelist(sendingWhitelist) && !hasValidWhitelist(receivingWhitelist)) {
throw new IllegalArgumentException("Listener has an empty sending and receiving whitelist.");
}
// Add listener to either or both processing queue
if (hasValidWhitelist(sendingWhitelist)) {
PacketFilterManager.verifyWhitelist(listener, sendingWhitelist);
serverProcessingQueue.addListener(handler, sendingWhitelist);
}
if (hasValidWhitelist(receivingWhitelist)) {
PacketFilterManager.verifyWhitelist(listener, receivingWhitelist);
clientProcessingQueue.addListener(handler, receivingWhitelist);
@@ -123,12 +194,55 @@ public class AsyncFilterManager implements AsynchronousManager {
handler.setNullPacketListener(new NullPacketListener(listener));
manager.addPacketListener(handler.getNullPacketListener());
}
return handler;
}
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
return whitelist != null && whitelist.getWhitelist().size() > 0;
return whitelist != null && whitelist.getTypes().size() > 0;
}
@Override
public void unregisterTimeoutHandler(PacketListener listener) {
if (listener == null)
throw new IllegalArgumentException("listener cannot be NULL.");
ListeningWhitelist sending = listener.getSendingWhitelist();
ListeningWhitelist receiving = listener.getReceivingWhitelist();
// Do it in the opposite order
if (serverTimeoutListeners.removeListener(listener, sending).size() > 0 ||
clientTimeoutListeners.removeListener(listener, receiving).size() > 0) {
timeoutListeners.remove(listener);
}
}
@Override
public void unregisterAsyncHandler(PacketListener listener) {
if (listener == null)
throw new IllegalArgumentException("listener cannot be NULL.");
AsyncListenerHandler handler =
findHandler(serverProcessingQueue, listener.getSendingWhitelist(), listener);
if (handler == null) {
handler = findHandler(clientProcessingQueue, listener.getReceivingWhitelist(), listener);
}
unregisterAsyncHandler(handler);
}
// Search for the first correct handler
private AsyncListenerHandler findHandler(PacketProcessingQueue queue, ListeningWhitelist search, PacketListener target) {
if (ListeningWhitelist.isEmpty(search))
return null;
for (PacketType type : search.getTypes()) {
for (PrioritizedListener<AsyncListenerHandler> element : queue.getListener(type)) {
if (element.getListener().getAsyncListener() == target) {
return element.getListener();
}
}
}
return null;
}
@Override
@@ -136,7 +250,7 @@ public class AsyncFilterManager implements AsynchronousManager {
if (handler == null)
throw new IllegalArgumentException("listenerToken cannot be NULL");
handler.cancel();
handler.cancel();
}
// Called by AsyncListenerHandler
@@ -152,18 +266,15 @@ public class AsyncFilterManager implements AsynchronousManager {
// Just remove it from the queue(s)
if (hasValidWhitelist(listener.getSendingWhitelist())) {
List<Integer> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
List<PacketType> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
// We're already taking care of this, so don't do anything
if (!cleaningUp)
serverQueue.signalPacketUpdate(removed, synchronusOK);
playerSendingHandler.sendServerPackets(removed, synchronusOK);
}
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
List<Integer> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
if (!cleaningUp)
clientQueue.signalPacketUpdate(removed, synchronusOK);
List<PacketType> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
playerSendingHandler.sendClientPackets(removed, synchronusOK);
}
}
@@ -184,7 +295,7 @@ public class AsyncFilterManager implements AsynchronousManager {
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
// Iterate through every packet listener
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
// Remove the listener
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
unregisterAsyncHandler(listener.getListener());
@@ -194,46 +305,61 @@ public class AsyncFilterManager implements AsynchronousManager {
/**
* Enqueue a packet for asynchronous processing.
*
* @param syncPacket - synchronous packet event.
* @param asyncMarker - the asynchronous marker to use.
*/
public synchronized void enqueueSyncPacket(PacketEvent syncPacket, AsyncMarker asyncMarker) {
PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker);
if (asyncMarker.isQueued() || asyncMarker.isTransmitted())
throw new IllegalArgumentException("Cannot queue a packet that has already been queued.");
asyncMarker.setQueuedSendingIndex(asyncMarker.getNewSendingIndex());
// Start the process
getSendingQueue(syncPacket).enqueue(newEvent);
// We know this is occuring on the main thread, so pass TRUE
getProcessingQueue(syncPacket).enqueue(newEvent, true);
// The player is only be null when they're logged out,
// so this should be a pretty safe check
Player player = newEvent.getPlayer();
if (player != null) {
// Start the process
getSendingQueue(syncPacket).enqueue(newEvent);
// We know this is occuring on the main thread, so pass TRUE
getProcessingQueue(syncPacket).enqueue(newEvent, true);
}
}
@Override
public Set<Integer> getSendingFilters() {
return PacketRegistry.toLegacy(serverProcessingQueue.keySet());
}
@Override
public Set<PacketType> getReceivingTypes() {
return serverProcessingQueue.keySet();
}
@Override
public Set<Integer> getReceivingFilters() {
return PacketRegistry.toLegacy(clientProcessingQueue.keySet());
}
@Override
public Set<PacketType> getSendingTypes() {
return clientProcessingQueue.keySet();
}
/**
* Used to create a default asynchronous task.
* @param plugin - the calling plugin.
* @param runnable - the runnable.
* Retrieve the current task scheduler.
* @return Current task scheduler.
*/
public void scheduleAsyncTask(Plugin plugin, Runnable runnable) {
scheduler.scheduleAsyncDelayedTask(plugin, runnable);
public BukkitScheduler getScheduler() {
return scheduler;
}
@Override
public boolean hasAsynchronousListeners(PacketEvent packet) {
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketID());
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketType());
return list != null && list.size() > 0;
}
@@ -242,23 +368,21 @@ public class AsyncFilterManager implements AsynchronousManager {
* @return Asynchronous marker.
*/
public AsyncMarker createAsyncMarker() {
return createAsyncMarker(AsyncMarker.DEFAULT_SENDING_DELTA, AsyncMarker.DEFAULT_TIMEOUT_DELTA);
return createAsyncMarker(AsyncMarker.DEFAULT_TIMEOUT_DELTA);
}
/**
* Construct an async marker with the given sending priority delta and timeout delta.
* @param sendingDelta - how many packets we're willing to wait.
* @param timeoutDelta - how long (in ms) until the packet expire.
* @return An async marker.
*/
public AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta) {
return createAsyncMarker(sendingDelta, timeoutDelta,
currentSendingIndex.incrementAndGet(), System.currentTimeMillis());
public AsyncMarker createAsyncMarker(long timeoutDelta) {
return createAsyncMarker(timeoutDelta, currentSendingIndex.incrementAndGet());
}
// Helper method
private AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta, long sendingIndex, long currentTime) {
return new AsyncMarker(manager, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta);
private AsyncMarker createAsyncMarker(long timeoutDelta, long sendingIndex) {
return new AsyncMarker(manager, sendingIndex, System.currentTimeMillis(), timeoutDelta);
}
@Override
@@ -267,17 +391,20 @@ public class AsyncFilterManager implements AsynchronousManager {
}
@Override
public Logger getLogger() {
return logger;
public ErrorReporter getErrorReporter() {
return reporter;
}
@Override
public void cleanupAll() {
cleaningUp = true;
serverProcessingQueue.cleanupAll();
serverQueue.cleanupAll();
playerSendingHandler.cleanupAll();
timeoutListeners.clear();
serverTimeoutListeners = null;
clientTimeoutListeners = null;
}
@Override
public void signalPacketTransmission(PacketEvent packet) {
signalPacketTransmission(packet, onMainThread());
@@ -298,8 +425,12 @@ public class AsyncFilterManager implements AsynchronousManager {
"A packet must have been queued before it can be transmitted.");
// Only send if the packet is ready
if (marker.decrementProcessingDelay() == 0) {
getSendingQueue(packet).signalPacketUpdate(packet, onMainThread);
if (marker.decrementProcessingDelay() == 0) {
PacketSendingQueue queue = getSendingQueue(packet, false);
// No need to create a new queue if the player has logged out
if (queue != null)
queue.signalPacketUpdate(packet, onMainThread);
}
}
@@ -308,8 +439,27 @@ public class AsyncFilterManager implements AsynchronousManager {
* @param packet - the packet.
* @return The server or client sending queue the packet belongs to.
*/
private PacketSendingQueue getSendingQueue(PacketEvent packet) {
return packet.isServerPacket() ? serverQueue : clientQueue;
public PacketSendingQueue getSendingQueue(PacketEvent packet) {
return playerSendingHandler.getSendingQueue(packet);
}
/**
* Retrieve the sending queue this packet belongs to.
* @param packet - the packet.
* @param createNew - if TRUE, create a new queue if it hasn't already been created.
* @return The server or client sending queue the packet belongs to.
*/
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
return playerSendingHandler.getSendingQueue(packet, createNew);
}
/**
* Retrieve the processing queue this packet belongs to.
* @param packet - the packet.
* @return The server or client sending processing the packet belongs to.
*/
public PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
}
/**
@@ -321,24 +471,24 @@ public class AsyncFilterManager implements AsynchronousManager {
}
/**
* Retrieve the processing queue this packet belongs to.
* @param packet - the packet.
* @return The server or client sending processing the packet belongs to.
* Send any due packets, or clean up packets that have expired.
* @param tickCounter Tick counter
* @param onMainThread Whether or not to execute on the main thread
*/
private PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
// The server queue is unlikely to need checking that often
if (tickCounter % 10 == 0) {
playerSendingHandler.trySendServerPackets(onMainThread);
}
playerSendingHandler.trySendClientPackets(onMainThread);
}
/**
* Send any due packets, or clean up packets that have expired.
* Clean up after a given player has logged out.
* @param player - the player that has just logged out.
*/
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
// The server queue is unlikely to need checking that often
if (tickCounter % 10 == 0) {
serverQueue.trySendPackets(onMainThread);
}
clientQueue.trySendPackets(onMainThread);
public void removePlayer(Player player) {
playerSendingHandler.removePlayer(player);
}
}
@@ -2,16 +2,16 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
@@ -20,25 +20,35 @@ package com.comphenix.protocol.async;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.timing.TimedListenerManager;
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
import com.comphenix.protocol.timing.TimedTracker;
import com.comphenix.protocol.utility.WrappedScheduler;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
/**
* Represents a handler for an asynchronous event.
*
* <p>
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until a certain condition has been met.
* @author Kristian
*/
public class AsyncListenerHandler {
public static final ReportType REPORT_HANDLER_NOT_STARTED = new ReportType(
"Plugin %s did not start the asynchronous handler %s by calling start() or syncStart().");
/**
* Signal an end to packet processing.
@@ -50,11 +60,16 @@ public class AsyncListenerHandler {
*/
private static final PacketEvent WAKEUP_PACKET = new PacketEvent(new Object());
/**
* The expected number of ticks per second.
*/
private static final int TICKS_PER_SECOND = 20;
// Unique worker ID
private static final AtomicInteger nextID = new AtomicInteger();
// Default queue capacity
private static int DEFAULT_CAPACITY = 1024;
private static final int DEFAULT_CAPACITY = 1024;
// Cancel the async handler
private volatile boolean cancelled;
@@ -76,10 +91,25 @@ public class AsyncListenerHandler {
private final Set<Integer> stoppedTasks = new HashSet<Integer>();
private final Object stopLock = new Object();
// Processing task on the main thread
private int syncTask = -1;
// Minecraft main thread
private Thread mainThread;
public AsyncListenerHandler(Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) {
// Warn plugins that the async listener handler must be started
private int warningTask;
// Timing manager
private TimedListenerManager timedManager = TimedListenerManager.getInstance();
/**
* Construct a manager for an asynchronous packet handler.
* @param mainThread - the main game thread.
* @param filterManager - the parent filter manager.
* @param listener - the current packet listener.
*/
AsyncListenerHandler(Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) {
if (filterManager == null)
throw new IllegalArgumentException("filterManager cannot be NULL");
if (listener == null)
@@ -88,12 +118,44 @@ public class AsyncListenerHandler {
this.mainThread = mainThread;
this.filterManager = filterManager;
this.listener = listener;
startWarningTask();
}
private void startWarningTask() {
warningTask = filterManager.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable() {
@Override
public void run() {
ProtocolLibrary.getErrorReporter().reportWarning(AsyncListenerHandler.this, Report.
newBuilder(REPORT_HANDLER_NOT_STARTED).
messageParam(listener.getPlugin(), AsyncListenerHandler.this).
build()
);
}
}, 2 * TICKS_PER_SECOND);
}
private void stopWarningTask() {
int taskId = warningTask;
// Ensure we have a task to cancel
if (warningTask >= 0) {
filterManager.getScheduler().cancelTask(taskId);
warningTask = -1;
}
}
/**
* Determine whether or not this asynchronous handler has been cancelled.
* @return TRUE if it has been cancelled/stopped, FALSE otherwise.
*/
public boolean isCancelled() {
return cancelled;
}
/**
* Retrieve the current asynchronous packet listener.
* @return Current packet listener.
*/
public PacketListener getAsyncListener() {
return listener;
}
@@ -113,10 +175,6 @@ public class AsyncListenerHandler {
PacketListener getNullPacketListener() {
return nullPacketListener;
}
private String getPluginName() {
return PacketAdapter.getPluginName(listener);
}
/**
* Retrieve the plugin associated with this async listener.
@@ -151,6 +209,7 @@ public class AsyncListenerHandler {
* close a specific worker is less efficient than stopping an arbitrary worker.
* <p>
* <b>Warning</b>: Never call the run() method in the main thread.
* @return The listener loop
*/
public AsyncRunnable getListenerLoop() {
return new AsyncRunnable() {
@@ -228,7 +287,8 @@ public class AsyncListenerHandler {
final AsyncRunnable listenerLoop = getListenerLoop();
filterManager.scheduleAsyncTask(listener.getPlugin(), new Runnable() {
stopWarningTask();
scheduleAsync(new Runnable() {
@Override
public void run() {
Thread thread = Thread.currentThread();
@@ -256,7 +316,7 @@ public class AsyncListenerHandler {
* public Void apply(&#64;Nullable AsyncRunnable workerLoop) {
* Thread thread = Thread.currentThread();
* int prevPriority = thread.getPriority();
*
*
* thread.setPriority(Thread.MIN_PRIORITY);
* workerLoop.run();
* thread.setPriority(prevPriority);
@@ -276,7 +336,7 @@ public class AsyncListenerHandler {
final AsyncRunnable listenerLoop = getListenerLoop();
final Function<AsyncRunnable, Void> delegateCopy = executor;
filterManager.scheduleAsyncTask(listener.getPlugin(), new Runnable() {
scheduleAsync(new Runnable() {
@Override
public void run() {
delegateCopy.apply(listenerLoop);
@@ -284,6 +344,11 @@ public class AsyncListenerHandler {
});
}
private void scheduleAsync(Runnable runnable) {
// Handle deprecation
WrappedScheduler.runAsynchronouslyRepeat(listener.getPlugin(), filterManager.getScheduler(), runnable, 0L, -1L);
}
/**
* Create a friendly thread name using the following convention:
* <p><code>
@@ -293,9 +358,9 @@ public class AsyncListenerHandler {
* @return A friendly thread name.
*/
public String getFriendlyWorkerName(int id) {
return String.format("Protocol Worker #%s - %s - [recv: %s, send: %s]",
id,
PacketAdapter.getPluginName(listener),
return String.format("Protocol Worker #%s - %s - [recv: %s, send: %s]",
id,
PacketAdapter.getPluginName(listener),
fromWhitelist(listener.getReceivingWhitelist()),
fromWhitelist(listener.getSendingWhitelist())
);
@@ -310,7 +375,107 @@ public class AsyncListenerHandler {
if (whitelist == null)
return "";
else
return Joiner.on(", ").join(whitelist.getWhitelist());
return Joiner.on(", ").join(whitelist.getTypes());
}
/**
* Start processing packets on the main thread.
* <p>
* This is useful if you need to synchronize with the main thread in your packet listener, but
* you're not performing any expensive processing.
* <p>
* <b>Note</b>: Use a asynchronous worker if the packet listener may use more than 0.5 ms
* of processing time on a single packet. Do as much as possible on the worker thread, and schedule synchronous tasks
* to use the Bukkit API instead.
* @return TRUE if the synchronized processing was successfully started, FALSE if it's already running.
* @throws IllegalStateException If we couldn't start the underlying task.
*/
public synchronized boolean syncStart() {
return syncStart(500, TimeUnit.MICROSECONDS);
}
/**
* Start processing packets on the main thread.
* <p>
* This is useful if you need to synchronize with the main thread in your packet listener, but
* you're not performing any expensive processing.
* <p>
* The processing time parameter gives the upper bound for the amount of time spent processing pending packets.
* It should be set to a fairly low number, such as 0.5 ms or 1% of a game tick - to reduce the impact
* on the main thread. Never go beyond 50 milliseconds.
* <p>
* <b>Note</b>: Use a asynchronous worker if the packet listener may exceed the ideal processing time
* on a single packet. Do as much as possible on the worker thread, and schedule synchronous tasks
* to use the Bukkit API instead.
*
* @param time - the amount of processing time alloted per game tick (20 ticks per second).
* @param unit - the unit of the processingTime argument.
* @return TRUE if the synchronized processing was successfully started, FALSE if it's already running.
* @throws IllegalStateException If we couldn't start the underlying task.
*/
public synchronized boolean syncStart(final long time, final TimeUnit unit) {
if (time <= 0)
throw new IllegalArgumentException("Time must be greater than zero.");
if (unit == null)
throw new IllegalArgumentException("TimeUnit cannot be NULL.");
final long tickDelay = 1;
final int workerID = nextID.incrementAndGet();
if (syncTask < 0) {
stopWarningTask();
syncTask = filterManager.getScheduler().scheduleSyncRepeatingTask(getPlugin(), new Runnable() {
@Override
public void run() {
long stopTime = System.nanoTime() + unit.convert(time, TimeUnit.NANOSECONDS);
while (!cancelled) {
PacketEvent packet = queuedPackets.poll();
if (packet == INTERUPT_PACKET || packet == WAKEUP_PACKET) {
// Sorry, asynchronous threads!
queuedPackets.add(packet);
// Try again next tick
break;
} else if (packet != null && packet.getAsyncMarker() != null) {
processPacket(workerID, packet, "onSyncPacket()");
} else {
// No more packets left - wait a tick
break;
}
// Check time here, ensuring that we at least process one packet
if (System.nanoTime() < stopTime)
break;
}
}
}, tickDelay, tickDelay);
// This is very bad - force the caller to handle it
if (syncTask < 0)
throw new IllegalStateException("Cannot start synchronous task.");
else
return true;
} else {
return false;
}
}
/**
* Stop processing packets on the main thread.
* @return TRUE if we stopped any processing tasks, FALSE if it has already been stopped.
*/
public synchronized boolean syncStop() {
if (syncTask > 0) {
filterManager.getScheduler().cancelTask(syncTask);
syncTask = -1;
return true;
} else {
return false;
}
}
/**
@@ -339,7 +504,7 @@ public class AsyncListenerHandler {
}
/**
* Set the current number of workers.
* Set the current number of workers.
* <p>
* This method can only be called with a count of zero when the listener is closing.
* @param count - new number of workers.
@@ -391,11 +556,15 @@ public class AsyncListenerHandler {
}
}
// DO NOT call this method from the main thread
/**
* The main processing loop of asynchronous threads.
* <p>
* Note: DO NOT call this method from the main thread
* @param workerID - the current worker ID.
*/
private void listenerLoop(int workerID) {
// Danger, danger!
if (Thread.currentThread().getId() == mainThread.getId())
if (Thread.currentThread().getId() == mainThread.getId())
throw new IllegalStateException("Do not call this method from the main thread.");
if (cancelled)
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
@@ -408,59 +577,26 @@ public class AsyncListenerHandler {
// Proceed
started.incrementAndGet();
mainLoop:
while (!cancelled) {
PacketEvent packet = queuedPackets.take();
AsyncMarker marker = packet.getAsyncMarker();
// Handle cancel requests
if (packet == null || marker == null || packet == INTERUPT_PACKET) {
return;
} else if (packet == WAKEUP_PACKET) {
if (packet == WAKEUP_PACKET) {
// This is a bit slow, but it should be safe
synchronized (stopLock) {
// Are we the one who is supposed to stop?
if (stoppedTasks.contains(workerID))
if (stoppedTasks.contains(workerID))
return;
if (waitForStops())
return;
}
} else if (packet == INTERUPT_PACKET) {
return;
}
// Here's the core of the asynchronous processing
try {
marker.setListenerHandler(this);
marker.setWorkerID(workerID);
synchronized (marker.getProcessingLock()) {
if (packet.isServerPacket())
listener.onPacketSending(packet);
else
listener.onPacketReceiving(packet);
}
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
filterManager.getLogger().log(Level.SEVERE,
"Unhandled exception occured in onAsyncPacket() for " + getPluginName(), e);
if (packet != null && packet.getAsyncMarker() != null) {
processPacket(workerID, packet, "onAsyncPacket()");
}
// Now, get the next non-cancelled listener
for (; marker.getListenerTraversal().hasNext(); ) {
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
if (!handler.isCancelled()) {
handler.enqueuePacket(packet);
continue mainLoop;
}
}
// There are no more listeners - queue the packet for transmission
filterManager.signalFreeProcessingSlot(packet);
// Note that listeners can opt to delay the packet transmission
filterManager.signalPacketTransmission(packet);
}
} catch (InterruptedException e) {
@@ -468,16 +604,87 @@ public class AsyncListenerHandler {
} finally {
// Clean up
started.decrementAndGet();
close();
}
}
/**
* Called when a packet is scheduled for processing.
* @param workerID - the current worker ID.
* @param packet - the current packet.
* @param methodName - name of the method.
*/
private void processPacket(int workerID, PacketEvent packet, String methodName) {
AsyncMarker marker = packet.getAsyncMarker();
// Here's the core of the asynchronous processing
try {
synchronized (marker.getProcessingLock()) {
marker.setListenerHandler(this);
marker.setWorkerID(workerID);
// We're not THAT worried about performance here
if (timedManager.isTiming()) {
// Retrieve the tracker to use
TimedTracker tracker = timedManager.getTracker(listener,
packet.isServerPacket() ? ListenerType.ASYNC_SERVER_SIDE : ListenerType.ASYNC_CLIENT_SIDE);
long token = tracker.beginTracking();
if (packet.isServerPacket())
listener.onPacketSending(packet);
else
listener.onPacketReceiving(packet);
// And we're done
tracker.endTracking(token, packet.getPacketType());
} else {
if (packet.isServerPacket())
listener.onPacketSending(packet);
else
listener.onPacketReceiving(packet);
}
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
filterManager.getErrorReporter().reportMinimal(listener.getPlugin(), methodName, e);
}
// Now, get the next non-cancelled listener
if (!marker.hasExpired()) {
for (; marker.getListenerTraversal().hasNext(); ) {
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
if (!handler.isCancelled()) {
handler.enqueuePacket(packet);
return;
}
}
}
// There are no more listeners - queue the packet for transmission
filterManager.signalFreeProcessingSlot(packet);
// Note that listeners can opt to delay the packet transmission
filterManager.signalPacketTransmission(packet);
}
/**
* Close all worker threads and the handler itself.
*/
private synchronized void close() {
// Remove the listener itself
if (!cancelled) {
filterManager.unregisterAsyncHandlerInternal(this);
cancelled = true;
// Close processing tasks
syncStop();
// Tell every uncancelled thread to end
stopThreads();
}
@@ -2,16 +2,16 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
@@ -24,14 +24,18 @@ import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import net.minecraft.server.Packet;
import java.util.logging.Level;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.primitives.Longs;
/**
@@ -51,7 +55,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* Default number of milliseconds until a packet will rejected.
*/
public static final int DEFAULT_TIMEOUT_DELTA = 60000;
public static final int DEFAULT_TIMEOUT_DELTA = 1800 * 1000;
/**
* Default number of packets to skip.
@@ -99,14 +103,14 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
private transient int workerID;
// Determine if Minecraft processes this packet asynchronously
private static Method isMinecraftAsync;
private static boolean alwaysSync;
private volatile static Method isMinecraftAsync;
private volatile static boolean alwaysSync;
/**
* Create a container for asyncronous packets.
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
*/
AsyncMarker(PacketStream packetStream, long sendingIndex, long sendingDelta, long initialTime, long timeoutDelta) {
AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) {
if (packetStream == null)
throw new IllegalArgumentException("packetStream cannot be NULL");
@@ -123,11 +127,11 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* Retrieve the time the packet was initially queued for asynchronous processing.
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
*/
public long getInitialTime() {
return initialTime;
}
}
/**
* Retrieve the time the packet will be forcefully rejected.
@@ -206,18 +210,16 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
}
/**
* Increment the number of times this packet must be signalled as done before its transmitted.
* Increment the number of times the current packet must be signalled as done before its transmitted.
* <p>
* This is useful if an asynchronous listener is waiting for further information before the
* packet can be sent to the user. A packet listener <b>MUST</b> eventually call
* packet can be sent to the user. A packet listener <b>MUST</b> eventually call
* {@link AsyncFilterManager#signalPacketTransmission(PacketEvent)},
* even if the packet is cancelled, after this method is called.
* even if the packet is cancelled, after this method is called.
* <p>
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
* using the {@link #getProcessingLock()} method.
* <p>
* To decrement the processing delay, call signalPacketUpdate. A thread that calls this method
* multiple times must call signalPacketUpdate at least that many times.
*
* @return The new processing delay.
*/
public int incrementProcessingDelay() {
@@ -333,7 +335,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
}
/**
* Set the current asynchronous listener handler.
* Set the current asynchronous listener handler.
* <p>
* Used by the worker to update the value.
* @param listenerHandler - new listener handler.
@@ -384,9 +386,9 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
void sendPacket(PacketEvent event) throws IOException {
try {
if (event.isServerPacket()) {
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), false);
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
} else {
packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), false);
packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
}
transmitted = true;
@@ -399,16 +401,17 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* Determine if Minecraft allows asynchronous processing of this packet.
* @param event - packet event
* @return TRUE if it does, FALSE otherwise.
* @throws FieldAccessException If determining fails for some reasaon
*/
public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException {
if (isMinecraftAsync == null && !alwaysSync) {
try {
isMinecraftAsync = FuzzyReflection.fromClass(Packet.class).getMethodByName("a_.*");
isMinecraftAsync = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethodByName("a_.*");
} catch (RuntimeException e) {
// This will occur in 1.2.5 (or possibly in later versions)
List<Method> methods = FuzzyReflection.fromClass(Packet.class).
List<Method> methods = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
getMethodListByParameters(boolean.class, new Class[] {});
// Try to look for boolean methods
@@ -417,18 +420,34 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
} else if (methods.size() == 1) {
// We're in 1.2.5
alwaysSync = true;
} else if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)) {
// The centralized async marker was removed in 1.8
// Incoming chat packets can be async
if (event.getPacketType() == PacketType.Play.Client.CHAT) {
String content = event.getPacket().getStrings().readSafely(0);
if (content != null) {
// Incoming chat packets are async only if they aren't commands
return ! content.startsWith("/");
} else {
ProtocolLibrary.log(Level.WARNING, "Failed to determine contents of incoming chat packet!");
alwaysSync = true;
}
} else {
// TODO: Find more cases of async packets
return false;
}
} else {
System.err.println("Cannot determine asynchronous state of packets!");
ProtocolLibrary.log(Level.INFO, "Could not determine asynchronous state of packets (this can probably be ignored)");
alwaysSync = true;
}
}
}
if (alwaysSync) {
return false;
} else {
try {
// Wrap exceptions
// Wrap exceptions
return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle());
} catch (IllegalArgumentException e) {
throw new FieldAccessException("Illegal argument", e);
@@ -447,4 +466,20 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
else
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
}
@Override
public boolean equals(Object other) {
// Standard equals
if (other == this)
return true;
if (other instanceof AsyncMarker)
return getNewSendingIndex() == ((AsyncMarker) other).getNewSendingIndex();
else
return false;
}
@Override
public int hashCode() {
return Longs.hashCode(getNewSendingIndex());
}
}
@@ -2,16 +2,16 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
@@ -35,6 +35,7 @@ public interface AsyncRunnable extends Runnable {
* <p>
* This may not occur right away.
* @return TRUE if the thread was stopped, FALSE if it was already stopped.
* @throws InterruptedException if it is interrupted
*/
public boolean stop() throws InterruptedException;
@@ -19,6 +19,7 @@ package com.comphenix.protocol.async;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
@@ -67,7 +68,8 @@ class NullPacketListener implements PacketListener {
private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) {
if (whitelist != null)
return new ListeningWhitelist(priority, whitelist.getWhitelist(), whitelist.getGamePhase());
// We don't use the Bukkit API, so don't engage the ProtocolLib synchronization code
return ListeningWhitelist.newBuilder(whitelist).priority(priority).mergeOptions(ListenerOptions.ASYNC).build();
else
return null;
}
@@ -20,6 +20,7 @@ package com.comphenix.protocol.async;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.base.Preconditions;
import com.google.common.collect.ComparisonChain;
import com.google.common.primitives.Longs;
/**
* Provides a comparable to a packet event.
@@ -56,4 +57,20 @@ class PacketEventHolder implements Comparable<PacketEventHolder> {
compare(sendingIndex, other.sendingIndex).
result();
}
@Override
public boolean equals(Object other) {
// Standard equals
if (other == this)
return true;
if (other instanceof PacketEventHolder)
return sendingIndex == ((PacketEventHolder) other).sendingIndex;
else
return false;
}
@Override
public int hashCode() {
return Longs.hashCode(sendingIndex);
}
}
@@ -19,10 +19,14 @@ package com.comphenix.protocol.async;
import java.util.Collection;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.Semaphore;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.MinMaxPriorityQueue;
@@ -34,6 +38,8 @@ import com.google.common.collect.MinMaxPriorityQueue;
* @author Kristian
*/
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
public static final ReportType REPORT_GUAVA_CORRUPT_MISSING =
new ReportType("Guava is either missing or corrupt. Reverting to PriorityQueue.");
// Initial number of elements
public static final int INITIAL_CAPACITY = 64;
@@ -58,23 +64,33 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
private Queue<PacketEventHolder> processingQueue;
// Packets for sending
private PacketSendingQueue sendingQueue;
public PacketProcessingQueue(PacketSendingQueue sendingQueue) {
this(sendingQueue, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
private PlayerSendingHandler sendingHandler;
public PacketProcessingQueue(PlayerSendingHandler sendingHandler) {
this(sendingHandler, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
}
public PacketProcessingQueue(PacketSendingQueue sendingQueue, int initialSize, int maximumSize, int maximumConcurrency) {
public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize, int maximumConcurrency) {
super();
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
expectedSize(initialSize).
maximumSize(maximumSize).
<PacketEventHolder>create(), null);
try {
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
expectedSize(initialSize).
maximumSize(maximumSize).
<PacketEventHolder>create(), null);
} catch (IncompatibleClassChangeError e) {
// Print in the console
ProtocolLibrary.getErrorReporter().reportWarning(
this, Report.newBuilder(REPORT_GUAVA_CORRUPT_MISSING).error(e));
// It's a Beta class after all
this.processingQueue = Synchronization.queue(
new PriorityQueue<PacketEventHolder>(), null);
}
this.maximumConcurrency = maximumConcurrency;
this.concurrentProcessing = new Semaphore(maximumConcurrency);
this.sendingQueue = sendingQueue;
this.sendingHandler = sendingHandler;
}
/**
@@ -115,7 +131,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
if (holder != null) {
PacketEvent packet = holder.getEvent();
AsyncMarker marker = packet.getAsyncMarker();
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketID());
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketType());
marker.incrementProcessingDelay();
@@ -131,8 +147,13 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
}
// The packet has no further listeners. Just send it.
if (marker.decrementProcessingDelay() == 0)
sendingQueue.signalPacketUpdate(packet, onMainThread);
if (marker.decrementProcessingDelay() == 0) {
PacketSendingQueue sendingQueue = sendingHandler.getSendingQueue(packet, false);
// In case the player has logged out
if (sendingQueue != null)
sendingQueue.signalPacketUpdate(packet, onMainThread);
}
signalProcessingDone();
} else {
@@ -168,5 +189,8 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
// Remove the rest, just in case
clearListeners();
// Remove every packet in the queue
processingQueue.clear();
}
}
@@ -21,10 +21,15 @@ import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.PriorityBlockingQueue;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.reflect.FieldAccessException;
@@ -33,14 +38,29 @@ import com.comphenix.protocol.reflect.FieldAccessException;
* Represents packets ready to be transmitted to a client.
* @author Kristian
*/
class PacketSendingQueue {
public static final int INITIAL_CAPACITY = 64;
abstract class PacketSendingQueue {
public static final ReportType REPORT_DROPPED_PACKET = new ReportType("Warning: Dropped packet index %s of type %s.");
public static final int INITIAL_CAPACITY = 10;
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
// Whether or not packet transmission can only occur on the main thread
private final boolean synchronizeMain;
// Asynchronous packet sending
private Executor asynchronousSender;
// Whether or not packet transmission must occur on a specific thread
private final boolean notThreadSafe;
// Whether or not we've run the cleanup procedure
private boolean cleanedUp = false;
/**
* Create a packet sending queue.
* @param notThreadSafe - whether or not to synchronize with the main thread or a background thread.
*/
public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) {
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
this.notThreadSafe = notThreadSafe;
this.asynchronousSender = asynchronousSender;
}
/**
* Number of packet events in the queue.
@@ -50,15 +70,6 @@ class PacketSendingQueue {
return sendingQueue.size();
}
/**
* Create a packet sending queue.
* @param synchronizeMain - whether or not to synchronize with the main thread.
*/
public PacketSendingQueue(boolean synchronizeMain) {
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
this.synchronizeMain = synchronizeMain;
}
/**
* Enqueue a packet for sending.
* @param packet - packet to queue.
@@ -77,10 +88,11 @@ class PacketSendingQueue {
AsyncMarker marker = packetUpdated.getAsyncMarker();
// Should we reorder the event?
if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex()) {
if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex() && !marker.hasExpired()) {
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
// "Cancel" the original event
packetUpdated.setReadOnly(false);
packetUpdated.setCancelled(true);
// Enqueue the copy with the new sending index
@@ -97,15 +109,14 @@ class PacketSendingQueue {
* @param packetsRemoved - packets that no longer have any listeners.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public synchronized void signalPacketUpdate(List<Integer> packetsRemoved, boolean onMainThread) {
Set<Integer> lookup = new HashSet<Integer>(packetsRemoved);
public synchronized void signalPacketUpdate(List<PacketType> packetsRemoved, boolean onMainThread) {
Set<PacketType> lookup = new HashSet<PacketType>(packetsRemoved);
// Note that this is O(n), so it might be expensive
for (PacketEventHolder holder : sendingQueue) {
PacketEvent event = holder.getEvent();
if (lookup.contains(event.getPacketID())) {
if (lookup.contains(event.getPacketType())) {
event.getAsyncMarker().setProcessed(true);
}
}
@@ -119,49 +130,115 @@ class PacketSendingQueue {
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public void trySendPackets(boolean onMainThread) {
// Whether or not to continue sending packets
boolean sending = true;
// Transmit as many packets as we can
while (true) {
PacketEventHolder holder = sendingQueue.peek();
while (sending) {
PacketEventHolder holder = sendingQueue.poll();
if (holder != null) {
PacketEvent current = holder.getEvent();
AsyncMarker marker = current.getAsyncMarker();
sending = processPacketHolder(onMainThread, holder);
if (!sending) {
// Add it back again
sendingQueue.add(holder);
}
// Abort if we're not on the main thread
if (synchronizeMain) {
} else {
// No more packets to send
sending = false;
}
}
}
/**
* Invoked when a packet might be ready for transmission.
* @param onMainThread - TRUE if we're on the main thread, FALSE otherwise.
* @param holder - packet container.
* @return TRUE to continue sending packets, FALSE otherwise.
*/
private boolean processPacketHolder(boolean onMainThread, final PacketEventHolder holder) {
PacketEvent current = holder.getEvent();
AsyncMarker marker = current.getAsyncMarker();
boolean hasExpired = marker.hasExpired();
// Guard in cause the queue is closed
if (cleanedUp) {
return true;
}
// End condition?
if (marker.isProcessed() || hasExpired) {
if (hasExpired) {
// Notify timeout listeners
onPacketTimeout(current);
// Recompute
marker = current.getAsyncMarker();
hasExpired = marker.hasExpired();
// Could happen due to the timeout listeners
if (!marker.isProcessed() && !hasExpired) {
return false;
}
}
// Is it okay to send the packet?
if (!current.isCancelled() && !hasExpired) {
// Make sure we're on the main thread
if (notThreadSafe) {
try {
boolean wantAsync = marker.isMinecraftAsync(current);
boolean wantSync = !wantAsync;
// Quit if we haven't fulfilled our promise
if ((onMainThread && wantAsync) || (!onMainThread && wantSync))
return;
// Wait for the next main thread heartbeat if we haven't fulfilled our promise
if (!onMainThread && wantSync) {
return false;
}
// Let's give it what it wants
if (onMainThread && wantAsync) {
asynchronousSender.execute(new Runnable() {
@Override
public void run() {
// We know this isn't on the main thread
processPacketHolder(false, holder);
}
});
// Scheduler will do the rest
return true;
}
} catch (FieldAccessException e) {
e.printStackTrace();
return;
// Just drop the packet
return true;
}
}
}
if (marker.isProcessed() || marker.hasExpired()) {
if (marker.isProcessed() && !current.isCancelled()) {
// Silently skip players that have logged out
if (isOnline(current.getPlayer())) {
sendPacket(current);
}
}
sendingQueue.poll();
continue;
// Silently skip players that have logged out
if (isOnline(current.getPlayer())) {
sendPacket(current);
}
}
}
// Only repeat when packets are removed
break;
// Drop the packet
return true;
}
// Add it back and stop sending
return false;
}
/**
* Invoked when a packet has timed out.
* @param event - the timed out packet.
*/
protected abstract void onPacketTimeout(PacketEvent event);
private boolean isOnline(Player player) {
return player != null && player.isOnline();
}
@@ -186,7 +263,7 @@ class PacketSendingQueue {
* @return TRUE if it must, FALSE otherwise.
*/
public boolean isSynchronizeMain() {
return synchronizeMain;
return notThreadSafe;
}
/**
@@ -204,10 +281,10 @@ class PacketSendingQueue {
}
} catch (PlayerLoggedOutException e) {
System.out.println(String.format(
"Warning: Dropped packet index %s of ID %s",
marker.getOriginalSendingIndex(), event.getPacketID()
));
ProtocolLibrary.getErrorReporter().reportDebug(this, Report.newBuilder(REPORT_DROPPED_PACKET).
messageParam(marker.getOriginalSendingIndex(), event.getPacketType()).
callerParam(event)
);
} catch (IOException e) {
// Just print the error
@@ -219,7 +296,12 @@ class PacketSendingQueue {
* Automatically transmits every delayed packet.
*/
public void cleanupAll() {
// Note that the cleanup itself will always occur on the main thread
forceSend();
if (!cleanedUp) {
// Note that the cleanup itself will always occur on the main thread
forceSend();
// And we're done
cleanedUp = true;
}
}
}
@@ -0,0 +1,264 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.async;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.concurrency.ConcurrentPlayerMap;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.SortedPacketListenerList;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
/**
* Contains every sending queue for every player.
*
* @author Kristian
*/
class PlayerSendingHandler {
private ErrorReporter reporter;
private ConcurrentMap<Player, QueueContainer> playerSendingQueues;
// Timeout listeners
private SortedPacketListenerList serverTimeoutListeners;
private SortedPacketListenerList clientTimeoutListeners;
// Asynchronous packet sending
private Executor asynchronousSender;
// Whether or not we're currently cleaning up
private volatile boolean cleaningUp;
/**
* Sending queues for a given player.
*
* @author Kristian
*/
private class QueueContainer {
private PacketSendingQueue serverQueue;
private PacketSendingQueue clientQueue;
public QueueContainer() {
// Server packets can be sent concurrently
serverQueue = new PacketSendingQueue(false, asynchronousSender) {
@Override
protected void onPacketTimeout(PacketEvent event) {
if (!cleaningUp) {
serverTimeoutListeners.invokePacketSending(reporter, event);
}
}
};
// Client packets must be synchronized
clientQueue = new PacketSendingQueue(true, asynchronousSender) {
@Override
protected void onPacketTimeout(PacketEvent event) {
if (!cleaningUp) {
clientTimeoutListeners.invokePacketSending(reporter, event);
}
}
};
}
public PacketSendingQueue getServerQueue() {
return serverQueue;
}
public PacketSendingQueue getClientQueue() {
return clientQueue;
}
}
/**
* Initialize a packet sending handler.
* @param reporter - error reporter.
* @param serverTimeoutListeners - set of server timeout listeners.
* @param clientTimeoutListeners - set of client timeout listeners.
*/
public PlayerSendingHandler(ErrorReporter reporter,
SortedPacketListenerList serverTimeoutListeners, SortedPacketListenerList clientTimeoutListeners) {
this.reporter = reporter;
this.serverTimeoutListeners = serverTimeoutListeners;
this.clientTimeoutListeners = clientTimeoutListeners;
// Initialize storage of queues
this.playerSendingQueues = ConcurrentPlayerMap.usingAddress();
}
/**
* Start the asynchronous packet sender.
*/
public synchronized void initializeScheduler() {
if (asynchronousSender == null) {
ThreadFactory factory = new ThreadFactoryBuilder().
setDaemon(true).
setNameFormat("ProtocolLib-AsyncSender %s").
build();
asynchronousSender = Executors.newSingleThreadExecutor(factory);
}
}
/**
* Retrieve the sending queue this packet belongs to.
* @param packet - the packet.
* @return The server or client sending queue the packet belongs to.
*/
public PacketSendingQueue getSendingQueue(PacketEvent packet) {
return getSendingQueue(packet, true);
}
/**
* Retrieve the sending queue this packet belongs to.
* @param packet - the packet.
* @param createNew - if TRUE, create a new queue if it hasn't already been created.
* @return The server or client sending queue the packet belongs to.
*/
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
QueueContainer queues = playerSendingQueues.get(packet.getPlayer());
// Safe concurrent initialization
if (queues == null && createNew) {
final QueueContainer newContainer = new QueueContainer();
// Attempt to map the queue
queues = playerSendingQueues.putIfAbsent(packet.getPlayer(), newContainer);
if (queues == null) {
queues = newContainer;
}
}
// Check for NULL again
if (queues != null)
return packet.isServerPacket() ? queues.getServerQueue() : queues.getClientQueue();
else
return null;
}
/**
* Send all pending packets.
*/
public void sendAllPackets() {
if (!cleaningUp) {
for (QueueContainer queues : playerSendingQueues.values()) {
queues.getClientQueue().cleanupAll();
queues.getServerQueue().cleanupAll();
}
}
}
/**
* Immediately send every server packet with the given list of IDs.
* @param types - types of every packet to send immediately.
* @param synchronusOK - whether or not we're running on the main thread.
*/
public void sendServerPackets(List<PacketType> types, boolean synchronusOK) {
if (!cleaningUp) {
for (QueueContainer queue : playerSendingQueues.values()) {
queue.getServerQueue().signalPacketUpdate(types, synchronusOK);
}
}
}
/**
* Immediately send every client packet with the given list of IDs.
* @param ids - ID of every packet to send immediately.
* @param synchronusOK - whether or not we're running on the main thread.
*/
public void sendClientPackets(List<PacketType> types, boolean synchronusOK) {
if (!cleaningUp) {
for (QueueContainer queue : playerSendingQueues.values()) {
queue.getClientQueue().signalPacketUpdate(types, synchronusOK);
}
}
}
/**
* Send any outstanding server packets.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public void trySendServerPackets(boolean onMainThread) {
for (QueueContainer queue : playerSendingQueues.values()) {
queue.getServerQueue().trySendPackets(onMainThread);
}
}
/**
* Send any outstanding server packets.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public void trySendClientPackets(boolean onMainThread) {
for (QueueContainer queue : playerSendingQueues.values()) {
queue.getClientQueue().trySendPackets(onMainThread);
}
}
/**
* Retrieve every server packet queue for every player.
* @return Every sever packet queue.
*/
public List<PacketSendingQueue> getServerQueues() {
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
for (QueueContainer queue : playerSendingQueues.values())
result.add(queue.getServerQueue());
return result;
}
/**
* Retrieve every client packet queue for every player.
* @return Every client packet queue.
*/
public List<PacketSendingQueue> getClientQueues() {
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
for (QueueContainer queue : playerSendingQueues.values())
result.add(queue.getClientQueue());
return result;
}
/**
* Send all pending packets and clean up queues.
*/
public void cleanupAll() {
if (!cleaningUp) {
cleaningUp = true;
sendAllPackets();
playerSendingQueues.clear();
}
}
/**
* Invoked when a player has just logged out.
* @param player - the player that just logged out.
*/
public void removePlayer(Player player) {
// Every packet will be dropped - there's nothing we can do
playerSendingQueues.remove(player);
}
}
@@ -0,0 +1,246 @@
package com.comphenix.protocol.collections;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Ticker;
import com.google.common.collect.Maps;
import com.google.common.primitives.Longs;
/**
* Represents a hash map where each association may expire after a given time has elapsed.
* <p>
* Note that replaced key-value associations are only collected once the original expiration time has elapsed.
*
* @author Kristian Stangeland
*
* @param <K> - type of the keys.
* @param <V> - type of the values.
*/
public class ExpireHashMap<K, V> {
private class ExpireEntry implements Comparable<ExpireEntry> {
public final long expireTime;
public final K expireKey;
public final V expireValue;
public ExpireEntry(long expireTime, K expireKey, V expireValue) {
this.expireTime = expireTime;
this.expireKey = expireKey;
this.expireValue = expireValue;
}
@Override
public int compareTo(ExpireEntry o) {
return Longs.compare(expireTime, o.expireTime);
}
@Override
public String toString() {
return "ExpireEntry [expireTime=" + expireTime + ", expireKey=" + expireKey
+ ", expireValue=" + expireValue + "]";
}
}
private Map<K, ExpireEntry> keyLookup = new HashMap<K, ExpireEntry>();
private PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<ExpireEntry>();
// View of keyLookup with direct values
private Map<K, V> valueView = Maps.transformValues(keyLookup, new Function<ExpireEntry, V>() {
@Override
public V apply(ExpireEntry entry) {
return entry.expireValue;
}
});
// Supplied by the constructor
private Ticker ticker;
/**
* Construct a new hash map where each entry may expire at a given time.
*/
public ExpireHashMap() {
this(Ticker.systemTicker());
}
/**
* Construct a new hash map where each entry may expire at a given time.
* @param ticker - supplier of the current time.
*/
public ExpireHashMap(Ticker ticker) {
this.ticker = ticker;
}
/**
* Retrieve the value associated with the given key, if it has not expired.
* @param key - the key.
* @return The value, or NULL if not found or it has expired.
*/
public V get(K key) {
evictExpired();
ExpireEntry entry = keyLookup.get(key);
return entry != null ? entry.expireValue : null;
}
/**
* Associate the given key with the given value, until the expire delay have elapsed.
* @param key - the key.
* @param value - the value.
* @param expireDelay - the amount of time until this association expires. Must be greater than zero.
* @param expireUnit - the unit of the expiration.
* @return Any previously unexpired association with this key, or NULL.
*/
public V put(K key, V value, long expireDelay, TimeUnit expireUnit) {
Preconditions.checkNotNull(expireUnit, "expireUnit cannot be NULL");
Preconditions.checkState(expireDelay > 0, "expireDelay cannot be equal or less than zero.");
evictExpired();
ExpireEntry entry = new ExpireEntry(
ticker.read() + TimeUnit.NANOSECONDS.convert(expireDelay, expireUnit),
key, value
);
ExpireEntry previous = keyLookup.put(key, entry);
// We enqueue its removal
expireQueue.add(entry);
return previous != null ? previous.expireValue : null;
}
/**
* Determine if the given key is referring to an unexpired association in the map.
* @param key - the key.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean containsKey(K key) {
evictExpired();
return keyLookup.containsKey(key);
}
/**
* Determine if the given value is referring to an unexpired association in the map.
* @param value - the value.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean containsValue(V value) {
evictExpired();
// Linear scan is the best we've got
for (ExpireEntry entry : keyLookup.values()) {
if (Objects.equal(value, entry.expireValue)) {
return true;
}
}
return false;
}
/**
* Remove a key and its associated value from the map.
* @param key - the key to remove.
* @return Value of the removed association, NULL otherwise.
*/
public V removeKey(K key) {
evictExpired();
ExpireEntry entry = keyLookup.remove(key);
return entry != null ? entry.expireValue : null;
}
/**
* Retrieve the number of entries in the map.
* @return The number of entries.
*/
public int size() {
evictExpired();
return keyLookup.size();
}
/**
* Retrieve a view of the keys in the current map.
* @return View of the keys.
*/
public Set<K> keySet() {
evictExpired();
return keyLookup.keySet();
}
/**
* Retrieve a view of all the values in the current map.
* @return All the values.
*/
public Collection<V> values() {
evictExpired();
return valueView.values();
}
/**
* Retrieve a view of all the entries in the set.
* @return All the entries.
*/
public Set<Entry<K, V>> entrySet() {
evictExpired();
return valueView.entrySet();
}
/**
* Retrieve a view of this expire map as an ordinary map that does not support insertion.
* @return The map.
*/
public Map<K, V> asMap() {
evictExpired();
return valueView;
}
/**
* Clear all references to key-value pairs that have been removed or replaced before they were naturally evicted.
* <p>
* This operation requires a linear scan of the current entries in the map.
*/
public void collect() {
// First evict what we can
evictExpired();
// Recreate the eviction queue - this is faster than removing entries in the old queue
expireQueue.clear();
expireQueue.addAll(keyLookup.values());
}
/**
* Clear all the entries in the current map.
*/
public void clear() {
keyLookup.clear();
expireQueue.clear();
}
/**
* Evict any expired entries in the map.
* <p>
* This is called automatically by any of the read or write operations.
*/
protected void evictExpired() {
long currentTime = ticker.read();
// Remove expired entries
while (expireQueue.size() > 0 && expireQueue.peek().expireTime <= currentTime) {
ExpireEntry entry = expireQueue.poll();
if (entry == keyLookup.get(entry.expireKey)) {
keyLookup.remove(entry.expireKey);
}
}
}
@Override
public String toString() {
return valueView.toString();
}
}
@@ -0,0 +1,140 @@
package com.comphenix.protocol.collections;
import java.util.Arrays;
import java.util.Map;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
/**
* Represents a very quick integer-based lookup map, with a fixed key space size.
* <p>
* Integers must be non-negative.
* @author Kristian
*/
public class IntegerMap<T> {
private T[] array;
private int size;
/**
* Construct a new integer map.
* @param <T> Parameter type
* @return A new integer map.
*/
public static <T> IntegerMap<T> newMap() {
return new IntegerMap<T>();
}
/**
* Construct a new integer map with a default capacity.
*/
public IntegerMap() {
this(8);
}
/**
* Construct a new integer map with a given capacity.
* @param initialCapacity - the capacity.
*/
public IntegerMap(int initialCapacity) {
@SuppressWarnings("unchecked")
T[] backingArray = (T[]) new Object[initialCapacity];
this.array = backingArray;
this.size = 0;
}
/**
* Associate an integer key with the given value.
* @param key - the integer key. Cannot be negative.
* @param value - the value. Cannot be NULL.
* @return The previous association, or NULL if not found.
*/
public T put(int key, T value) {
ensureCapacity(key);
T old = array[key];
array[key] = Preconditions.checkNotNull(value, "value cannot be NULL");
if (old == null)
size++;
return old;
}
/**
* Remove an association from the map.
* @param key - the key of the association to remove.
* @return The old associated value, or NULL.
*/
public T remove(int key) {
T old = array[key];
array[key] = null;
if (old != null)
size--;
return old;
}
/**
* Resize the backing array to fit the given key.
* @param key - the key.
*/
protected void ensureCapacity(int key) {
int newLength = array.length;
// Don't resize if the key fits
if (key < 0)
throw new IllegalArgumentException("Negative key values are not permitted.");
if (key < newLength)
return;
while (newLength <= key) {
int next = newLength * 2;
// Handle overflow
newLength = next > newLength ? next : Integer.MAX_VALUE;
}
this.array = Arrays.copyOf(array, newLength);
}
/**
* Retrieve the number of mappings in this map.
* @return The number of mapping.
*/
public int size() {
return size;
}
/**
* Retrieve the value associated with a given key.
* @param key - the key.
* @return The value, or NULL if not found.
*/
public T get(int key) {
if (key >= 0 && key < array.length)
return array[key];
return null;
}
/**
* Determine if the given key exists in the map.
* @param key - the key to check.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean containsKey(int key) {
return get(key) != null;
}
/**
* Convert the current map to an Integer map.
* @return The Integer map.
*/
public Map<Integer, Object> toMap() {
Map<Integer, Object> map = Maps.newHashMap();
for (int i = 0; i < array.length; i++) {
if (array[i] != null) {
map.put(i, array[i]);
}
}
return map;
}
}
@@ -0,0 +1,67 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.guava;
import java.io.DataInputStream;
import java.util.Set;
import java.util.logging.Level;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.google.common.collect.Range;
/**
* @author dmulloy2
*/
public class Guava {
private static GuavaCompat compat;
static {
try {
Range.closed(1, 2);
compat = new Guava17();
} catch (Throwable ex) {
try {
Class<?> clazz = Class.forName("com.comphenix.protocol.compat.guava.Guava10");
compat = (GuavaCompat) clazz.newInstance();
} catch (Throwable ex1) {
ProtocolLibrary.log(Level.SEVERE, "Failed to create Guava 10 compat:", ex1);
}
}
}
private static GuavaCompat getCompat() {
return compat;
}
public static <C extends Comparable<C>> Range<C> closedRange(C lower, C upper) {
return getCompat().closedRange(lower, upper);
}
public static <C extends Comparable<C>> Range<C> singleton(C singleton) {
return getCompat().singletonRange(singleton);
}
public static Set<Integer> toSet(Range<Integer> range) {
return getCompat().toSet(range);
}
public static DataInputStream addHeader(DataInputStream input, PacketType type) {
return getCompat().addHeader(input, type);
}
}
@@ -0,0 +1,76 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.guava;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Range;
import com.google.common.io.ByteSource;
/**
* @author dmulloy2
*/
public class Guava17 implements GuavaCompat {
@Override
public <C extends Comparable<C>> Range<C> closedRange(C lower, C upper) {
return Range.closed(lower, upper);
}
@Override
public <C extends Comparable<C>> Range<C> singletonRange(C singleton) {
return Range.singleton(singleton);
}
@Override
public Set<Integer> toSet(Range<Integer> range) {
return ContiguousSet.create(range, DiscreteDomain.integers());
}
@Override
public DataInputStream addHeader(final DataInputStream input, final PacketType type) {
ByteSource header = new ByteSource() {
@Override
public InputStream openStream() throws IOException {
byte[] data = new byte[] { (byte) type.getLegacyId() };
return new ByteArrayInputStream(data);
}
};
ByteSource data = new ByteSource() {
@Override
public InputStream openStream() throws IOException {
return input;
}
};
// Combine them into a single stream
try {
return new DataInputStream(ByteSource.concat(header, data).openStream());
} catch (IOException e) {
throw new RuntimeException("Cannot add header.", e);
}
}
}
@@ -0,0 +1,38 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.guava;
import java.io.DataInputStream;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.google.common.collect.Range;
/**
* @author dmulloy2
*/
public interface GuavaCompat {
<C extends Comparable<C>> Range<C> closedRange(C lower, C upper);
<C extends Comparable<C>> Range<C> singletonRange(C singleton);
Set<Integer> toSet(Range<Integer> range);
DataInputStream addHeader(DataInputStream input, PacketType type);
}
@@ -0,0 +1,28 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty;
import com.comphenix.protocol.injector.netty.Injector;
/**
* @author dmulloy2
*/
public interface ChannelInjector extends Injector {
WrappedChannel getChannel();
}
@@ -0,0 +1,103 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* @author dmulloy2
*/
public class LegacyProtocolRegistry extends ProtocolRegistry {
public LegacyProtocolRegistry() {
super();
}
@Override
protected void initialize() {
final Object[] protocols = enumProtocol.getEnumConstants();
List<Map<Integer, Class<?>>> serverMaps = Lists.newArrayList();
List<Map<Integer, Class<?>>> clientMaps = Lists.newArrayList();
StructureModifier<Object> modifier = null;
// Result
Register result = new Register();
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Integer, Class<?>>> maps = modifier.withTarget(protocol).withType(Map.class);
serverMaps.add(maps.read(0));
clientMaps.add(maps.read(1));
}
// Maps we have to occationally check have changed
for (Map<Integer, Class<?>> map : Iterables.concat(serverMaps, clientMaps)) {
result.containers.add(new MapContainer(map));
}
// Heuristic - there are more server packets than client packets
if (sum(clientMaps) > sum(serverMaps)) {
// Swap if this is violated
List<Map<Integer, Class<?>>> temp = serverMaps;
serverMaps = clientMaps;
clientMaps = temp;
}
for (int i = 0; i < protocols.length; i++) {
Enum<?> enumProtocol = (Enum<?>) protocols[i];
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
associatePackets(result, serverMaps.get(i), equivalent, Sender.SERVER);
associatePackets(result, clientMaps.get(i), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
@Override
protected void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
for (Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromID(protocol, sender, entry.getKey(), entry.getValue());
try {
register.typeToClass.put(type, entry.getValue());
if (sender == Sender.SERVER)
register.serverPackets.add(type);
if (sender == Sender.CLIENT)
register.clientPackets.add(type);
} catch (IllegalArgumentException ex) {
// Sometimes this happens with fake packets, just ignore it
}
}
}
}
@@ -0,0 +1,99 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.logging.Level;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.compat.netty.independent.IndependentNetty;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
/**
* @author dmulloy2
*/
public class Netty {
private static NettyCompat compat;
static {
try {
Class.forName("io.netty.buffer.ByteBuf");
compat = new IndependentNetty();
} catch (ClassNotFoundException ex) {
try {
Class<?> clazz = Class.forName("com.comphenix.protocol.compat.netty.shaded.ShadedNetty");
compat = (NettyCompat) clazz.newInstance();
} catch (Exception ex1) {
ProtocolLibrary.log(Level.SEVERE, "Failed to create legacy netty compat:", ex1);
}
}
}
private static NettyCompat getCompat() {
return compat;
}
public static boolean isIndependent() {
return compat instanceof IndependentNetty;
}
public static WrappedByteBuf createPacketBuffer() {
return getCompat().createPacketBuffer();
}
public static Class<?> getGenericFutureListenerArray() {
return getCompat().getGenericFutureListenerArray();
}
public static Class<?> getChannelHandlerContext() {
return getCompat().getChannelHandlerContext();
}
public static String toEncodedText(CompressedImage image) {
return getCompat().toEncodedText(image);
}
public static WrappedByteBuf decode(byte[] encoded) {
return getCompat().decode(encoded);
}
public static ProtocolInjector getProtocolInjector(Plugin library, PacketFilterManager packetFilterManager, ErrorReporter reporter) {
return getCompat().getProtocolInjector(library, packetFilterManager, reporter);
}
public static WrappedByteBuf packetReader(DataInputStream input) {
return getCompat().packetReader(input);
}
public static WrappedByteBuf packetWriter(DataOutputStream output) {
return getCompat().packetWriter(output);
}
public static WrappedByteBuf copiedBuffer(byte[] array) {
return getCompat().copiedBuffer(array);
}
public static WrappedByteBuf buffer() {
return getCompat().buffer();
}
}
@@ -0,0 +1,55 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
/**
* @author dmulloy2
*/
// TODO: Sort out packet readers/writers
public interface NettyCompat {
WrappedByteBuf createPacketBuffer();
WrappedByteBuf copiedBuffer(byte[] array);
WrappedByteBuf buffer();
Class<?> getGenericFutureListenerArray();
Class<?> getChannelHandlerContext();
String toEncodedText(CompressedImage image);
WrappedByteBuf decode(byte[] encoded);
ProtocolInjector getProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter);
WrappedByteBuf packetReader(DataInputStream input);
WrappedByteBuf packetWriter(DataOutputStream output);
}
@@ -0,0 +1,38 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
/**
* @author dmulloy2
*/
public interface ProtocolInjector extends ChannelListener {
PlayerInjectionHandler getPlayerInjector();
PacketInjector getPacketInjector();
void setDebug(boolean debug);
void inject();
void close();
}
@@ -0,0 +1,46 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* @author dmulloy2
*/
public interface WrappedByteBuf {
void writeBytes(ObjectInputStream input, int id) throws IOException;
Object getHandle();
int readableBytes();
void readBytes(ObjectOutputStream output, int readableBytes) throws IOException;
void readBytes(byte[] data);
void writeByte(byte b);
void writeByte(int i);
void writeBytes(byte[] bytes);
byte[] array();
}
@@ -0,0 +1,26 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty;
/**
* @author dmulloy2
*/
public interface WrappedChannel {
void writeAndFlush(Object packet);
}
@@ -0,0 +1,105 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.base64.Base64;
import io.netty.util.concurrent.GenericFutureListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.compat.netty.NettyCompat;
import com.comphenix.protocol.compat.netty.ProtocolInjector;
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
import com.google.common.base.Charsets;
/**
* @author dmulloy2
*/
public class IndependentNetty implements NettyCompat {
@Override
public WrappedByteBuf createPacketBuffer() {
ByteBuf buffer = UnpooledByteBufAllocator.DEFAULT.buffer();
Class<?> packetSerializer = MinecraftReflection.getPacketDataSerializerClass();
try {
return new NettyByteBuf((ByteBuf) packetSerializer.getConstructor(ByteBuf.class).newInstance(buffer));
} catch (Exception e) {
throw new RuntimeException("Cannot construct packet serializer.", e);
}
}
@Override
public Class<?> getGenericFutureListenerArray() {
return GenericFutureListener[].class;
}
@Override
public Class<?> getChannelHandlerContext() {
return ChannelHandlerContext.class;
}
@Override
public String toEncodedText(CompressedImage image) {
final ByteBuf buffer = Unpooled.wrappedBuffer(image.getDataCopy());
String computed = "data:" + image.getMime() + ";base64," +
Base64.encode(buffer).toString(Charsets.UTF_8);
return computed;
}
@Override
public WrappedByteBuf decode(byte[] encoded) {
return new NettyByteBuf(Base64.decode(Unpooled.wrappedBuffer(encoded)));
}
@Override
public ProtocolInjector getProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) {
return new NettyProtocolInjector(plugin, invoker, reporter);
}
@Override
public WrappedByteBuf packetReader(DataInputStream input) {
return new NettyByteBuf(NettyByteBufAdapter.packetReader(input));
}
@Override
public WrappedByteBuf packetWriter(DataOutputStream output) {
return new NettyByteBuf(NettyByteBufAdapter.packetWriter(output));
}
@Override
public WrappedByteBuf copiedBuffer(byte[] array) {
return new NettyByteBuf(Unpooled.copiedBuffer(array));
}
@Override
public WrappedByteBuf buffer() {
return new NettyByteBuf(Unpooled.buffer());
}
}
@@ -0,0 +1,234 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.Callable;
import com.google.common.collect.Lists;
public class NettyBootstrapList implements List<Object> {
private List<Object> delegate;
private ChannelHandler handler;
/**
* Construct a new bootstrap list.
* @param delegate - the delegate.
* @param handler - the channel handler to add.
*/
public NettyBootstrapList(List<Object> delegate, ChannelHandler handler) {
this.delegate = delegate;
this.handler = handler;
// Process all existing bootstraps
for (Object item : this) {
processElement(item);
}
}
@Override
public synchronized boolean add(Object element) {
processElement(element);
return delegate.add(element);
}
@Override
public synchronized boolean addAll(Collection<? extends Object> collection) {
List<Object> copy = Lists.newArrayList(collection);
// Process the collection before we pass it on
for (Object element : copy) {
processElement(element);
}
return delegate.addAll(copy);
}
@Override
public synchronized Object set(int index, Object element) {
Object old = delegate.set(index, element);
// Handle the old future, and the newly inserted future
if (old != element) {
unprocessElement(old);
processElement(element);
}
return old;
}
/**
* Process a single element.
* @param element - the element.
*/
protected void processElement(Object element) {
if (element instanceof ChannelFuture) {
processBootstrap((ChannelFuture) element);
}
}
/**
* Unprocess a single element.
* @param element - the element to unprocess.
*/
protected void unprocessElement(Object element) {
if (element instanceof ChannelFuture) {
unprocessBootstrap((ChannelFuture) element);
}
}
/**
* Process a single channel future.
* @param future - the future.
*/
protected void processBootstrap(ChannelFuture future) {
// Important: Must be addFirst()
future.channel().pipeline().addFirst(handler);
}
/**
* Revert any changes we made to the channel future.
* @param future - the future.
*/
protected void unprocessBootstrap(ChannelFuture future) {
final Channel channel = future.channel();
// For thread safety - see ChannelInjector.close()
channel.eventLoop().submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
channel.pipeline().remove(handler);
return null;
}
});
}
/**
* Close and revert all changes.
*/
public synchronized void close() {
for (Object element : this)
unprocessElement(element);
}
// Boiler plate
@Override
public synchronized int size() {
return delegate.size();
}
@Override
public synchronized boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean contains(Object o) {
return delegate.contains(o);
}
@Override
public synchronized Iterator<Object> iterator() {
return delegate.iterator();
}
@Override
public synchronized Object[] toArray() {
return delegate.toArray();
}
@Override
public synchronized <T> T[] toArray(T[] a) {
return delegate.toArray(a);
}
@Override
public synchronized boolean remove(Object o) {
return delegate.remove(o);
}
@Override
public synchronized boolean containsAll(Collection<?> c) {
return delegate.containsAll(c);
}
@Override
public synchronized boolean addAll(int index, Collection<? extends Object> c) {
return delegate.addAll(index, c);
}
@Override
public synchronized boolean removeAll(Collection<?> c) {
return delegate.removeAll(c);
}
@Override
public synchronized boolean retainAll(Collection<?> c) {
return delegate.retainAll(c);
}
@Override
public synchronized void clear() {
delegate.clear();
}
@Override
public synchronized Object get(int index) {
return delegate.get(index);
}
@Override
public synchronized void add(int index, Object element) {
delegate.add(index, element);
}
@Override
public synchronized Object remove(int index) {
return delegate.remove(index);
}
@Override
public synchronized int indexOf(Object o) {
return delegate.indexOf(o);
}
@Override
public synchronized int lastIndexOf(Object o) {
return delegate.lastIndexOf(o);
}
@Override
public synchronized ListIterator<Object> listIterator() {
return delegate.listIterator();
}
@Override
public synchronized ListIterator<Object> listIterator(int index) {
return delegate.listIterator(index);
}
@Override
public synchronized List<Object> subList(int fromIndex, int toIndex) {
return delegate.subList(fromIndex, toIndex);
}
// End boiler plate
}
@@ -0,0 +1,83 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
/**
* @author dmulloy2
*/
public class NettyByteBuf implements WrappedByteBuf {
private WeakReference<ByteBuf> handle;
public NettyByteBuf(ByteBuf handle) {
this.handle = new WeakReference<ByteBuf>(handle);
}
@Override
public void writeBytes(ObjectInputStream input, int id) throws IOException {
handle.get().writeBytes(input, id);
}
@Override
public Object getHandle() {
return handle.get();
}
@Override
public int readableBytes() {
return handle.get().readableBytes();
}
@Override
public void readBytes(ObjectOutputStream output, int readableBytes) throws IOException {
handle.get().readBytes(output, readableBytes);
}
@Override
public void readBytes(byte[] data) {
handle.get().readBytes(data);
}
@Override
public void writeByte(byte b) {
handle.get().writeByte(b);
}
@Override
public void writeByte(int i) {
handle.get().writeByte(i);
}
@Override
public void writeBytes(byte[] bytes) {
handle.get().writeBytes(bytes);
}
@Override
public byte[] array() {
return handle.get().array();
}
}
@@ -0,0 +1,392 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.buffer.AbstractByteBuf;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.channels.WritableByteChannel;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.io.ByteStreams;
/**
* Construct a ByteBuf around an input stream and an output stream.
* <p>
* Note that as streams usually don't support seeking, this implementation will ignore
* all indexing in the byte buffer.
* @author Kristian
*/
public class NettyByteBufAdapter extends AbstractByteBuf {
private DataInputStream input;
private DataOutputStream output;
// For modifying the reader or writer index
private static FieldAccessor READER_INDEX;
private static FieldAccessor WRITER_INDEX;
private static final int CAPACITY = Short.MAX_VALUE;
private NettyByteBufAdapter(DataInputStream input, DataOutputStream output) {
// Just pick a figure
super(CAPACITY);
this.input = input;
this.output = output;
// Prepare accessors
try {
if (READER_INDEX == null) {
READER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("readerIndex"));
}
if (WRITER_INDEX == null) {
WRITER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("writerIndex"));
}
} catch (Exception e) {
throw new RuntimeException("Cannot initialize ByteBufAdapter.", e);
}
// "Infinite" reading/writing
if (input == null)
READER_INDEX.set(this, Integer.MAX_VALUE);
if (output == null)
WRITER_INDEX.set(this, Integer.MAX_VALUE);
}
/**
* Construct a new Minecraft packet serializer using the current byte buf adapter.
* @param input - the input stream.
* @return A packet serializer with a wrapped byte buf adapter.
*/
public static ByteBuf packetReader(DataInputStream input) {
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(input, null));
}
/**
* Construct a new Minecraft packet deserializer using the current byte buf adapter.
* @param output - the output stream.
* @return A packet serializer with a wrapped byte buf adapter.
*/
public static ByteBuf packetWriter(DataOutputStream output) {
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(null, output));
}
@Override
public int refCnt() {
return 1;
}
@Override
public boolean release() {
return false;
}
@Override
public boolean release(int paramInt) {
return false;
}
@Override
protected byte _getByte(int paramInt) {
try {
return input.readByte();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
}
@Override
protected short _getShort(int paramInt) {
try {
return input.readShort();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
}
@Override
protected int _getUnsignedMedium(int paramInt) {
try {
return input.readUnsignedShort();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
}
@Override
protected int _getInt(int paramInt) {
try {
return input.readInt();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
}
@Override
protected long _getLong(int paramInt) {
try {
return input.readLong();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
}
@Override
protected void _setByte(int index, int value) {
try {
output.writeByte(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
}
@Override
protected void _setShort(int index, int value) {
try {
output.writeShort(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
}
@Override
protected void _setMedium(int index, int value) {
try {
output.writeShort(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
}
@Override
protected void _setInt(int index, int value) {
try {
output.writeInt(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
}
@Override
protected void _setLong(int index, long value) {
try {
output.writeLong(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
}
@Override
public int capacity() {
return CAPACITY;
}
@Override
public ByteBuf capacity(int paramInt) {
return this;
}
@Override
public ByteBufAllocator alloc() {
return null;
}
@Override
public ByteOrder order() {
return ByteOrder.LITTLE_ENDIAN;
}
@Override
public ByteBuf unwrap() {
return null;
}
@Override
public boolean isDirect() {
return false;
}
@Override
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
try {
for (int i = 0; i < length; i++) {
dst.setByte(dstIndex + i, input.read());
}
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
return this;
}
@Override
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
try {
input.read(dst, dstIndex, length);
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
return this;
}
@Override
public ByteBuf getBytes(int index, ByteBuffer dst) {
try {
dst.put(ByteStreams.toByteArray(input));
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
return this;
}
@Override
public ByteBuf getBytes(int index, OutputStream dst, int length) throws IOException {
ByteStreams.copy(ByteStreams.limit(input, length), dst);
return this;
}
@Override
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
byte[] data = ByteStreams.toByteArray(ByteStreams.limit(input, length));
out.write(ByteBuffer.wrap(data));
return data.length;
}
@Override
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
byte[] buffer = new byte[length];
src.getBytes(srcIndex, buffer);
try {
output.write(buffer);
return this;
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
}
@Override
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
try {
output.write(src, srcIndex, length);
return this;
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
}
@Override
public ByteBuf setBytes(int index, ByteBuffer src) {
try {
WritableByteChannel channel = Channels.newChannel(output);
channel.write(src);
return this;
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
}
@Override
public int setBytes(int index, InputStream in, int length) throws IOException {
InputStream limit = ByteStreams.limit(in, length);
ByteStreams.copy(limit, output);
return length - limit.available();
}
@Override
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(length);
WritableByteChannel channel = Channels.newChannel(output);
int count = in.read(buffer);
channel.write(buffer);
return count;
}
@Override
public ByteBuf copy(int index, int length) {
throw new UnsupportedOperationException("Cannot seek in input stream.");
}
@Override
public int nioBufferCount() {
return 0;
}
@Override
public ByteBuffer nioBuffer(int paramInt1, int paramInt2) {
throw new UnsupportedOperationException();
}
@Override
public ByteBuffer internalNioBuffer(int paramInt1, int paramInt2) {
return null;
}
@Override
public ByteBuffer[] nioBuffers(int paramInt1, int paramInt2) {
return null;
}
@Override
public boolean hasArray() {
return false;
}
@Override
public byte[] array() {
return null;
}
@Override
public int arrayOffset() {
return 0;
}
@Override
public boolean hasMemoryAddress() {
return false;
}
@Override
public long memoryAddress() {
return 0;
}
@Override
public ByteBuf retain(int paramInt) {
return this;
}
@Override
public ByteBuf retain() {
return this;
}
}
@@ -0,0 +1,38 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.channel.Channel;
import com.comphenix.protocol.compat.netty.WrappedChannel;
/**
* @author dmulloy2
*/
public class NettyChannel implements WrappedChannel {
private final Channel channel;
public NettyChannel(Channel channel) {
this.channel = channel;
}
@Override
public void writeAndFlush(Object packet) {
channel.writeAndFlush(packet);
}
}
@@ -0,0 +1,932 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.internal.TypeParameterMatcher;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.ListIterator;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.compat.netty.ChannelInjector;
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
import com.comphenix.protocol.compat.netty.WrappedChannel;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.NetworkProcessor;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.NettyNetworkMarker;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.google.common.base.Preconditions;
import com.google.common.collect.MapMaker;
/**
* Represents a channel injector.
* @author Kristian
*/
public class NettyChannelInjector extends ByteToMessageDecoder implements ChannelInjector {
public static final ReportType REPORT_CANNOT_INTERCEPT_SERVER_PACKET = new ReportType("Unable to intercept a written server packet.");
public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet.");
public static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread.");
public static final ReportType REPORT_CANNOT_FIND_GET_VERSION = new ReportType("Cannot find getVersion() in NetworkMananger");
public static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s");
/**
* Indicates that a packet has bypassed packet listeners.
*/
private static final PacketEvent BYPASSED_PACKET = new PacketEvent(NettyChannelInjector.class);
// The login packet
private static Class<?> PACKET_LOGIN_CLIENT = null;
private static FieldAccessor LOGIN_GAME_PROFILE = null;
// Saved accessors
private static MethodAccessor DECODE_BUFFER;
private static MethodAccessor ENCODE_BUFFER;
private static FieldAccessor ENCODER_TYPE_MATCHER;
// For retrieving the protocol
private static FieldAccessor PROTOCOL_ACCESSOR;
// For retrieving the protocol version
private static MethodAccessor PROTOCOL_VERSION;
// The factory that created this injector
private NettyInjectionFactory factory;
// The player, or temporary player
private Player player;
private Player updated;
private String playerName;
// The player connection
private Object playerConnection;
// The current network manager and channel
private final Object networkManager;
private final Channel originalChannel;
private VolatileField channelField;
// Known network markers
private ConcurrentMap<Object, NetworkMarker> packetMarker = new MapMaker().weakKeys().makeMap();
/**
* Indicate that this packet has been processed by event listeners.
* <p>
* This must never be set outside the channel pipeline's thread.
*/
private PacketEvent currentEvent;
/**
* A packet event that should be processed by the write method.
*/
private PacketEvent finalEvent;
/**
* A flag set by the main thread to indiciate that a packet should not be processed.
*/
private final ThreadLocal<Boolean> scheduleProcessPackets = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return true;
};
};
// Other handlers
private ByteToMessageDecoder vanillaDecoder;
private MessageToByteEncoder<Object> vanillaEncoder;
private Deque<PacketEvent> finishQueue = new ArrayDeque<PacketEvent>();
// The channel listener
private ChannelListener channelListener;
// Processing network markers
private NetworkProcessor processor;
// Closed
private boolean injected;
private boolean closed;
/**
* Construct a new channel injector.
* @param player - the current player, or temporary player.
* @param networkManager - its network manager.
* @param channel - its channel.
* @param channelListener - a listener.
* @param factory - the factory that created this injector
*/
public NettyChannelInjector(Player player, Object networkManager, Channel channel, ChannelListener channelListener, NettyInjectionFactory factory) {
this.player = Preconditions.checkNotNull(player, "player cannot be NULL");
this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL");
this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL");
this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL");
this.factory = Preconditions.checkNotNull(factory, "factory cannot be NULL");
this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter());
// Get the channel field
this.channelField = new VolatileField(FuzzyReflection.fromObject(networkManager, true).getFieldByType("channel", Channel.class),
networkManager, true);
}
/**
* Get the version of the current protocol.
* @return The version.
*/
@Override
public int getProtocolVersion() {
MethodAccessor accessor = PROTOCOL_VERSION;
if (accessor == null) {
try {
accessor = Accessors.getMethodAccessor(networkManager.getClass(), "getVersion");
} catch (Throwable ex) {
}
}
if (accessor != null) {
return (Integer) accessor.invoke(networkManager);
} else {
return MinecraftProtocolVersion.getCurrentVersion();
}
}
@Override
@SuppressWarnings("unchecked")
public boolean inject() {
synchronized (networkManager) {
if (closed)
return false;
if (originalChannel instanceof Factory)
return false;
if (!originalChannel.isActive())
return false;
// Main thread? We should synchronize with the channel thread, otherwise we might see a
// pipeline with only some of the handlers removed
if (Bukkit.isPrimaryThread()) {
// Just like in the close() method, we'll avoid blocking the main thread
executeInChannelThread(new Runnable() {
@Override
public void run() {
inject();
}
});
return false; // We don't know
}
// Don't inject the same channel twice
if (findChannelHandler(originalChannel, NettyChannelInjector.class) != null) {
return false;
}
// Get the vanilla decoder, so we don't have to replicate the work
vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder");
vanillaEncoder = (MessageToByteEncoder<Object>) originalChannel.pipeline().get("encoder");
if (vanillaDecoder == null)
throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline() );
if (vanillaEncoder == null)
throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline() );
patchEncoder(vanillaEncoder);
if (DECODE_BUFFER == null)
DECODE_BUFFER = Accessors.getMethodAccessor(vanillaDecoder.getClass(),
"decode", ChannelHandlerContext.class, ByteBuf.class, List.class);
if (ENCODE_BUFFER == null)
ENCODE_BUFFER = Accessors.getMethodAccessor(vanillaEncoder.getClass(),
"encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
// Intercept sent packets
MessageToByteEncoder<Object> protocolEncoder = new MessageToByteEncoder<Object>() {
@Override
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
if (packet instanceof WirePacket) {
// Special case for wire format
NettyChannelInjector.this.encodeWirePacket((WirePacket) packet, new NettyByteBuf(output));
} else {
NettyChannelInjector.this.encode(ctx, packet, output);
}
}
@Override
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
super.write(ctx, packet, promise);
NettyChannelInjector.this.finalWrite(ctx, packet, promise);
}
};
// Intercept recieved packets
ChannelInboundHandlerAdapter finishHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// Execute context first
ctx.fireChannelRead(msg);
NettyChannelInjector.this.finishRead(ctx, msg);
}
};
// Insert our handlers - note that we effectively replace the vanilla encoder/decoder
originalChannel.pipeline().addBefore("decoder", "protocol_lib_decoder", this);
originalChannel.pipeline().addBefore("protocol_lib_decoder", "protocol_lib_finish", finishHandler);
originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder);
// Intercept all write methods
channelField.setValue(new NettyChannelProxy(originalChannel, MinecraftReflection.getPacketClass()) {
// Compatibility with Spigot 1.8
private final NettyPipelineProxy pipelineProxy = new NettyPipelineProxy(originalChannel.pipeline(), this) {
@Override
public ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler) {
// Correct the position of the decoder
if ("decoder".equals(baseName)) {
if (super.get("protocol_lib_decoder") != null && guessCompression(handler)) {
super.addBefore("protocol_lib_decoder", name, handler);
return this;
}
}
return super.addBefore(baseName, name, handler);
}
};
@Override
public ChannelPipeline pipeline() {
return pipelineProxy;
}
@Override
protected <T> Callable<T> onMessageScheduled(final Callable<T> callable, FieldAccessor packetAccessor) {
final PacketEvent event = handleScheduled(callable, packetAccessor);
// Handle cancelled events
if (event != null && event.isCancelled())
return null;
return new Callable<T>() {
@Override
public T call() throws Exception {
T result = null;
// This field must only be updated in the pipeline thread
currentEvent = event;
result = callable.call();
currentEvent = null;
return result;
}
};
}
@Override
protected Runnable onMessageScheduled(final Runnable runnable, FieldAccessor packetAccessor) {
final PacketEvent event = handleScheduled(runnable, packetAccessor);
// Handle cancelled events
if (event != null && event.isCancelled())
return null;
return new Runnable() {
@Override
public void run() {
currentEvent = event;
runnable.run();
currentEvent = null;
}
};
}
protected PacketEvent handleScheduled(Object instance, FieldAccessor accessor) {
// Let the filters handle this packet
Object original = accessor.get(instance);
// See if we've been instructed not to process packets
if (!scheduleProcessPackets.get()) {
NetworkMarker marker = getMarker(original);
if (marker != null) {
PacketEvent result = new PacketEvent(NettyChannelInjector.class);
result.setNetworkMarker(marker);
return result;
} else {
return BYPASSED_PACKET;
}
}
PacketEvent event = processSending(original);
if (event != null && !event.isCancelled()) {
Object changed = event.getPacket().getHandle();
// Change packet to be scheduled
if (original != changed)
accessor.set(instance, changed);
};
return event != null ? event : BYPASSED_PACKET;
}
});
injected = true;
return true;
}
}
/**
* Determine if the given object is a compressor or decompressor.
* @param handler - object to test.
* @return TRUE if it is, FALSE if not or unknown.
*/
private boolean guessCompression(ChannelHandler handler) {
String className = handler != null ? handler.getClass().getCanonicalName() : "";
return className.contains("Compressor") || className.contains("Decompressor");
}
/**
* Process a given message on the packet listeners.
* @param message - the message/packet.
* @return The resulting message/packet.
*/
private PacketEvent processSending(Object message) {
return channelListener.onPacketSending(NettyChannelInjector.this, message, getMarker(message));
}
/**
* This method patches the encoder so that it skips already created packets.
* @param encoder - the encoder to patch.
*/
private void patchEncoder(MessageToByteEncoder<Object> encoder) {
if (ENCODER_TYPE_MATCHER == null) {
ENCODER_TYPE_MATCHER = Accessors.getFieldAccessor(encoder.getClass(), "matcher", true);
}
ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass()));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (channelListener.isDebug())
cause.printStackTrace();
super.exceptionCaught(ctx, cause);
}
protected void encodeWirePacket(WirePacket packet, WrappedByteBuf output) throws Exception {
packet.writeId(output);
packet.writeBytes(output);
}
/**
* Encode a packet to a byte buffer, taking over for the standard Minecraft encoder.
* @param ctx - the current context.
* @param packet - the packet to encode to a byte array.
* @param output - the output byte array.
* @throws Exception If anything went wrong.
*/
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
NetworkMarker marker = null;
PacketEvent event = currentEvent;
try {
// Skip every kind of non-filtered packet
if (!scheduleProcessPackets.get()) {
return;
}
// This packet has not been seen by the main thread
if (event == null) {
Class<?> clazz = packet.getClass();
// Schedule the transmission on the main thread instead
if (channelListener.hasMainThreadListener(clazz)) {
// Delay the packet
scheduleMainThread(packet);
packet = null;
} else {
event = processSending(packet);
// Handle the output
if (event != null) {
packet = !event.isCancelled() ? event.getPacket().getHandle() : null;
}
}
}
if (event != null) {
// Retrieve marker without accidentally constructing it
marker = NetworkMarker.getNetworkMarker(event);
}
// Process output handler
if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) {
ByteBuf packetBuffer = ctx.alloc().buffer();
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, packetBuffer);
// Let each handler prepare the actual output
byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer));
// Write the result
output.writeBytes(data);
packet = null;
// Sent listeners?
finalEvent = event;
return;
}
} catch (Exception e) {
channelListener.getReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_INTERCEPT_SERVER_PACKET).callerParam(packet).error(e).build());
} finally {
// Attempt to handle the packet nevertheless
if (packet != null) {
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, output);
finalEvent = event;
}
}
}
/**
* Invoked when a packet has been written to the channel.
* @param ctx - current context.
* @param packet - the packet that has been written.
* @param promise - a promise.
*/
protected void finalWrite(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) {
PacketEvent event = finalEvent;
if (event != null) {
// Necessary to prevent infinite loops
finalEvent = null;
currentEvent = null;
processor.invokePostEvent(event, NetworkMarker.getNetworkMarker(event));
}
}
private void scheduleMainThread(final Object packetCopy) {
// Don't use BukkitExecutors for this - it has a bit of overhead
Bukkit.getScheduler().scheduleSyncDelayedTask(factory.getPlugin(), new Runnable() {
@Override
public void run() {
invokeSendPacket(packetCopy);
}
});
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception {
byteBuffer.markReaderIndex();
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
try {
// Reset queue
finishQueue.clear();
for (ListIterator<Object> it = packets.listIterator(); it.hasNext(); ) {
Object input = it.next();
Class<?> packetClass = input.getClass();
NetworkMarker marker = null;
// Special case!
handleLogin(packetClass, input);
if (channelListener.includeBuffer(packetClass)) {
byteBuffer.resetReaderIndex();
marker = new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer));
}
PacketEvent output = channelListener.onPacketReceiving(this, input, marker);
// Handle packet changes
if (output != null) {
if (output.isCancelled()) {
it.remove();
continue;
} else if (output.getPacket().getHandle() != input) {
it.set(output.getPacket().getHandle());
}
finishQueue.addLast(output);
}
}
} catch (Exception e) {
channelListener.getReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build());
}
}
/**
* Invoked after our decoder.
* @param ctx - current context.
* @param msg - the current packet.
*/
protected void finishRead(ChannelHandlerContext ctx, Object msg) {
// Assume same order
PacketEvent event = finishQueue.pollFirst();
if (event != null) {
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
if (marker != null) {
processor.invokePostEvent(event, marker);
}
}
}
/**
* Invoked when we may need to handle the login packet.
* @param packetClass - the packet class.
* @param packet - the packet.
*/
protected void handleLogin(Class<?> packetClass, Object packet) {
Class<?> loginClass = PACKET_LOGIN_CLIENT;
FieldAccessor loginClient = LOGIN_GAME_PROFILE;
// Initialize packet class and login
if (loginClass == null) {
loginClass = PacketType.Login.Client.START.getPacketClass();
PACKET_LOGIN_CLIENT = loginClass;
}
if (loginClient == null) {
loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, MinecraftReflection.getGameProfileClass(), true);
LOGIN_GAME_PROFILE = loginClient;
}
// See if we are dealing with the login packet
if (loginClass.equals(packetClass)) {
// GameProfile profile = (GameProfile) loginClient.get(packet);
WrappedGameProfile profile = WrappedGameProfile.fromHandle(loginClient.get(packet));
// Save the channel injector
factory.cacheInjector(profile.getName(), this);
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
// See NetworkManager.channelActive(ChannelHandlerContext) for why
if (channelField != null) {
channelField.refreshValue();
}
}
/**
* Retrieve every byte in the given byte buffer.
* @param buffer - the buffer.
* @return The bytes.
*/
private byte[] getBytes(ByteBuf buffer) {
byte[] data = new byte[buffer.readableBytes()];
buffer.readBytes(data);
return data;
}
/**
* Disconnect the current player.
* @param message - the disconnect message, if possible.
*/
private void disconnect(String message) {
// If we're logging in, we can only close the channel
if (playerConnection == null || player instanceof Factory) {
originalChannel.disconnect();
} else {
// Call the disconnect method
try {
MinecraftMethods.getDisconnectMethod(playerConnection.getClass()).
invoke(playerConnection, message);
} catch (Exception e) {
throw new IllegalArgumentException("Unable to invoke disconnect method.", e);
}
}
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
saveMarker(packet, marker);
try {
scheduleProcessPackets.set(filtered);
invokeSendPacket(packet);
} finally {
scheduleProcessPackets.set(true);
}
}
/**
* Invoke the sendPacket method in Minecraft.
* @param packet - the packet to send.
*/
private void invokeSendPacket(Object packet) {
// Attempt to send the packet with NetworkMarker.handle(), or the PlayerConnection if its active
try {
if (player instanceof Factory) {
MinecraftMethods.getNetworkManagerHandleMethod().invoke(networkManager, packet, new GenericFutureListener[0]);
} else {
MinecraftMethods.getSendPacketMethod().invoke(getPlayerConnection(), packet);
}
} catch (Throwable ex) {
ProtocolLibrary.getErrorReporter().reportWarning(factory.getPlugin(),
Report.newBuilder(REPORT_CANNOT_SEND_PACKET).messageParam(packet, playerName).error(ex).build());
}
}
@Override
public void recieveClientPacket(final Object packet) {
// TODO: Ensure the packet listeners are executed in the channel thread.
// Execute this in the channel thread
Runnable action = new Runnable() {
@Override
public void run() {
try {
MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(networkManager, null, packet);
} catch (Exception e) {
// Inform the user
ProtocolLibrary.getErrorReporter().reportMinimal(factory.getPlugin(), "recieveClientPacket", e);
}
}
};
// Execute in the worker thread
if (originalChannel.eventLoop().inEventLoop()) {
action.run();
} else {
originalChannel.eventLoop().execute(action);
}
}
@Override
public Protocol getCurrentProtocol() {
if (PROTOCOL_ACCESSOR == null) {
PROTOCOL_ACCESSOR = Accessors.getFieldAccessor(
networkManager.getClass(), MinecraftReflection.getEnumProtocolClass(), true);
}
return Protocol.fromVanilla((Enum<?>) PROTOCOL_ACCESSOR.get(networkManager));
}
/**
* Retrieve the player connection of the current player.
* @return The player connection.
*/
private Object getPlayerConnection() {
if (playerConnection == null) {
playerConnection = MinecraftFields.getPlayerConnection(player);
}
return playerConnection;
}
@Override
public NetworkMarker getMarker(Object packet) {
return packetMarker.get(packet);
}
@Override
public void saveMarker(Object packet, NetworkMarker marker) {
if (marker != null) {
packetMarker.put(packet, marker);
}
}
@Override
public Player getPlayer() {
if (player == null && playerName != null) {
return Bukkit.getPlayer(playerName);
}
return player;
}
/**
* Set the player instance.
* @param player - current instance.
*/
@Override
public void setPlayer(Player player) {
this.player = player;
this.playerName = player.getName();
}
/**
* Set the updated player instance.
* @param updated - updated instance.
*/
@Override
public void setUpdatedPlayer(Player updated) {
this.updated = updated;
this.playerName = updated.getName();
}
@Override
public boolean isInjected() {
return injected;
}
/**
* Determine if this channel has been closed and cleaned up.
* @return TRUE if it has, FALSE otherwise.
*/
@Override
public boolean isClosed() {
return closed;
}
@Override
public void close() {
if (!closed) {
closed = true;
if (injected) {
channelField.revertValue();
// Calling remove() in the main thread will block the main thread, which may lead
// to a deadlock:
// http://pastebin.com/L3SBVKzp
//
// ProtocolLib executes this close() method through a PlayerQuitEvent in the main thread,
// which has implicitly aquired a lock on SimplePluginManager (see SimplePluginManager.callEvent(Event)).
// Unfortunately, the remove() method will schedule the removal on one of the Netty worker threads if
// it's called from a different thread, blocking until the removal has been confirmed.
//
// This is bad enough (Rule #1: Don't block the main thread), but the real trouble starts if the same
// worker thread happens to be handling a server ping connection when this removal task is scheduled.
// In that case, it may attempt to invoke an asynchronous ServerPingEvent (see PacketStatusListener)
// using SimplePluginManager.callEvent(). But, since this has already been locked by the main thread,
// we end up with a deadlock. The main thread is waiting for the worker thread to process the task, and
// the worker thread is waiting for the main thread to finish executing PlayerQuitEvent.
//
// TL;DR: Concurrency is hard.
executeInChannelThread(new Runnable() {
@Override
public void run() {
String[] handlers = new String[] {
"protocol_lib_decoder", "protocol_lib_finish", "protocol_lib_encoder"
};
for (String handler : handlers) {
try {
originalChannel.pipeline().remove(handler);
} catch (NoSuchElementException e) {
// Ignore
}
}
}
});
// Clear cache
factory.invalidate(player);
// Clear player instances
// Should help fix memory leaks
this.player = null;
this.updated = null;
}
}
}
/**
* Execute a specific command in the channel thread.
* <p>
* Exceptions are printed through the standard error reporter mechanism.
* @param command - the command to execute.
*/
private void executeInChannelThread(final Runnable command) {
originalChannel.eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
command.run();
} catch (Exception e) {
ProtocolLibrary.getErrorReporter().reportDetailed(NettyChannelInjector.this,
Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build());
}
}
});
}
/**
* Find the first channel handler that is assignable to a given type.
* @param channel - the channel.
* @param clazz - the type.
* @return The first handler, or NULL.
*/
public static ChannelHandler findChannelHandler(Channel channel, Class<?> clazz) {
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
if (clazz.isAssignableFrom(entry.getValue().getClass())) {
return entry.getValue();
}
}
return null;
}
/**
* Represents a socket injector that foreards to the current channel injector.
* @author Kristian
*/
public static class ChannelSocketInjector implements SocketInjector {
private final NettyChannelInjector injector;
public ChannelSocketInjector(NettyChannelInjector injector) {
this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL");
}
@Override
public Socket getSocket() throws IllegalAccessException {
return NettySocketAdapter.adapt((SocketChannel) injector.originalChannel);
}
@Override
public SocketAddress getAddress() throws IllegalAccessException {
return injector.originalChannel.remoteAddress();
}
@Override
public void disconnect(String message) throws InvocationTargetException {
injector.disconnect(message);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
injector.sendServerPacket(packet, marker, filtered);
}
@Override
public Player getPlayer() {
return injector.getPlayer();
}
@Override
public Player getUpdatedPlayer() {
return injector.updated;
}
@Override
public void transferState(SocketInjector delegate) {
// Do nothing
}
@Override
public void setUpdatedPlayer(Player updatedPlayer) {
injector.setPlayer(updatedPlayer);
}
public NettyChannelInjector getChannelInjector() {
return injector;
}
}
@Override
public WrappedChannel getChannel() {
return new NettyChannel(originalChannel);
}
}
@@ -0,0 +1,334 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelProgressivePromise;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import java.lang.reflect.Field;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.Callable;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.google.common.collect.Maps;
public abstract class NettyChannelProxy implements Channel {
// Mark that a certain object does not contain a message field
private static final FieldAccessor MARK_NO_MESSAGE = new FieldAccessor() {
@Override
public void set(Object instance, Object value) { }
@Override
public Object get(Object instance) { return null; }
@Override
public Field getField() { return null; };
};
// Looking up packets in inner classes
private static Map<Class<?>, FieldAccessor> MESSAGE_LOOKUP = Maps.newConcurrentMap();
// The underlying channel
protected Channel delegate;
protected Class<?> messageClass;
// Event loop proxy
private transient NettyEventLoopProxy loopProxy;
public NettyChannelProxy(Channel delegate, Class<?> messageClass) {
this.delegate = delegate;
this.messageClass = messageClass;
}
/**
* Invoked when a packet is scheduled for transmission in the event loop.
* @param <T> Type
* @param callable - callable to schedule for execution.
* @param packetAccessor - accessor for modifying the packet in the callable.
* @return The callable that will be scheduled, or NULL to cancel.
*/
protected abstract <T> Callable<T> onMessageScheduled(Callable<T> callable, FieldAccessor packetAccessor);
/**
* Invoked when a packet is scheduled for transmission in the event loop.
* @param runnable - the runnable that contains a packet to be scheduled.
* @param packetAccessor - accessor for modifying the packet in the runnable.
* @return The runnable that will be scheduled, or NULL to cancel.
*/
protected abstract Runnable onMessageScheduled(Runnable runnable, FieldAccessor packetAccessor);
@Override
public <T> Attribute<T> attr(AttributeKey<T> paramAttributeKey) {
return delegate.attr(paramAttributeKey);
}
@Override
public ChannelFuture bind(SocketAddress paramSocketAddress) {
return delegate.bind(paramSocketAddress);
}
@Override
public ChannelPipeline pipeline() {
return delegate.pipeline();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress) {
return delegate.connect(paramSocketAddress);
}
@Override
public ByteBufAllocator alloc() {
return delegate.alloc();
}
@Override
public ChannelPromise newPromise() {
return delegate.newPromise();
}
@Override
public EventLoop eventLoop() {
if (loopProxy == null) {
loopProxy = new NettyEventLoopProxy() {
@Override
protected EventLoop getDelegate() {
return delegate.eventLoop();
}
@Override
protected Runnable schedulingRunnable(final Runnable runnable) {
final FieldAccessor accessor = getMessageAccessor(runnable);
if (accessor != null) {
Runnable result = onMessageScheduled(runnable, accessor);;
return result != null ? result : getEmptyRunnable();
}
return runnable;
}
@Override
protected <T> Callable<T> schedulingCallable(Callable<T> callable) {
FieldAccessor accessor = getMessageAccessor(callable);
if (accessor != null) {
Callable<T> result = onMessageScheduled(callable, accessor);;
return result != null ? result : NettyEventLoopProxy.<T>getEmptyCallable();
}
return callable;
}
};
}
return loopProxy;
}
/**
* Retrieve a way to access the packet field of an object.
* @param value - the object.
* @return The packet field accessor, or NULL if not found.
*/
private FieldAccessor getMessageAccessor(Object value) {
Class<?> clazz = value.getClass();
FieldAccessor accessor = MESSAGE_LOOKUP.get(clazz);
if (accessor == null) {
try {
accessor = Accessors.getFieldAccessor(clazz, messageClass, true);
} catch (IllegalArgumentException e) {
accessor = MARK_NO_MESSAGE;
}
// Save the result
MESSAGE_LOOKUP.put(clazz, accessor);
}
return accessor != MARK_NO_MESSAGE ? accessor : null;
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress1,
SocketAddress paramSocketAddress2) {
return delegate.connect(paramSocketAddress1, paramSocketAddress2);
}
@Override
public ChannelProgressivePromise newProgressivePromise() {
return delegate.newProgressivePromise();
}
@Override
public Channel parent() {
return delegate.parent();
}
@Override
public ChannelConfig config() {
return delegate.config();
}
@Override
public ChannelFuture newSucceededFuture() {
return delegate.newSucceededFuture();
}
@Override
public boolean isOpen() {
return delegate.isOpen();
}
@Override
public ChannelFuture disconnect() {
return delegate.disconnect();
}
@Override
public boolean isRegistered() {
return delegate.isRegistered();
}
@Override
public ChannelFuture newFailedFuture(Throwable paramThrowable) {
return delegate.newFailedFuture(paramThrowable);
}
@Override
public ChannelFuture close() {
return delegate.close();
}
@Override
public boolean isActive() {
return delegate.isActive();
}
@Override
@Deprecated
public ChannelFuture deregister() {
return delegate.deregister();
}
@Override
public ChannelPromise voidPromise() {
return delegate.voidPromise();
}
@Override
public ChannelMetadata metadata() {
return delegate.metadata();
}
@Override
public ChannelFuture bind(SocketAddress paramSocketAddress,
ChannelPromise paramChannelPromise) {
return delegate.bind(paramSocketAddress, paramChannelPromise);
}
@Override
public SocketAddress localAddress() {
return delegate.localAddress();
}
@Override
public SocketAddress remoteAddress() {
return delegate.remoteAddress();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress,
ChannelPromise paramChannelPromise) {
return delegate.connect(paramSocketAddress, paramChannelPromise);
}
@Override
public ChannelFuture closeFuture() {
return delegate.closeFuture();
}
@Override
public boolean isWritable() {
return delegate.isWritable();
}
@Override
public Channel flush() {
return delegate.flush();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress1,
SocketAddress paramSocketAddress2, ChannelPromise paramChannelPromise) {
return delegate.connect(paramSocketAddress1, paramSocketAddress2, paramChannelPromise);
}
@Override
public Channel read() {
return delegate.read();
}
@Override
public Unsafe unsafe() {
return delegate.unsafe();
}
@Override
public ChannelFuture disconnect(ChannelPromise paramChannelPromise) {
return delegate.disconnect(paramChannelPromise);
}
@Override
public ChannelFuture close(ChannelPromise paramChannelPromise) {
return delegate.close(paramChannelPromise);
}
@Override
@Deprecated
public ChannelFuture deregister(ChannelPromise paramChannelPromise) {
return delegate.deregister(paramChannelPromise);
}
@Override
public ChannelFuture write(Object paramObject) {
return delegate.write(paramObject);
}
@Override
public ChannelFuture write(Object paramObject, ChannelPromise paramChannelPromise) {
return delegate.write(paramObject, paramChannelPromise);
}
@Override
public ChannelFuture writeAndFlush(Object paramObject, ChannelPromise paramChannelPromise) {
return delegate.writeAndFlush(paramObject, paramChannelPromise);
}
@Override
public ChannelFuture writeAndFlush(Object paramObject) {
return delegate.writeAndFlush(paramObject);
}
@Override
public int compareTo(Channel o) {
return delegate.compareTo(o);
}
}
@@ -0,0 +1,259 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ProgressivePromise;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* An event loop proxy.
* @author Kristian.
*/
abstract class NettyEventLoopProxy implements EventLoop {
private static final Runnable EMPTY_RUNNABLE = new Runnable() {
@Override
public void run() {
// Do nothing
}
};
private static final Callable<?> EMPTY_CALLABLE = new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
};
};
/**
* Retrieve the underlying event loop.
* @return The event loop.
*/
protected abstract EventLoop getDelegate();
/**
* Retrieve a callable that does nothing but return NULL.
* @return The empty callable.
*/
@SuppressWarnings("unchecked")
public static <T> Callable<T> getEmptyCallable() {
return (Callable<T>) EMPTY_CALLABLE;
}
/**
* Retrieve a runnable that does nothing.
* @return A NO-OP runnable.
*/
public static Runnable getEmptyRunnable() {
return EMPTY_RUNNABLE;
}
/**
* Invoked when a runnable is being scheduled.
* @param runnable - the runnable that is scheduling.
* @return The runnable to schedule instead. Cannot be NULL.
*/
protected abstract Runnable schedulingRunnable(Runnable runnable);
/**
* Invoked when a callable is being scheduled.
* @param runnable - the callable that is scheduling.
* @return The callable to schedule instead. Cannot be NULL.
*/
protected abstract <T> Callable<T> schedulingCallable(Callable<T> callable);
@Override
public void execute(Runnable command) {
getDelegate().execute(schedulingRunnable(command));
}
@Override
public <T> Future<T> submit(Callable<T> action) {
return getDelegate().submit(schedulingCallable(action));
}
@Override
public <T> Future<T> submit(Runnable action, T arg1) {
return getDelegate().submit(schedulingRunnable(action), arg1);
}
@Override
public Future<?> submit(Runnable action) {
return getDelegate().submit(schedulingRunnable(action));
}
@Override
public <V> ScheduledFuture<V> schedule(Callable<V> action, long arg1, TimeUnit arg2) {
return getDelegate().schedule(schedulingCallable(action), arg1, arg2);
}
@Override
public ScheduledFuture<?> schedule(Runnable action, long arg1, TimeUnit arg2) {
return getDelegate().schedule(schedulingRunnable(action), arg1, arg2);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable action, long arg1, long arg2, TimeUnit arg3) {
return getDelegate().scheduleAtFixedRate(schedulingRunnable(action), arg1, arg2, arg3);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable action, long arg1, long arg2, TimeUnit arg3) {
return getDelegate().scheduleWithFixedDelay(schedulingRunnable(action), arg1, arg2, arg3);
}
// Boiler plate:
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return getDelegate().awaitTermination(timeout, unit);
}
@Override
public boolean inEventLoop() {
return getDelegate().inEventLoop();
}
@Override
public boolean inEventLoop(Thread arg0) {
return getDelegate().inEventLoop(arg0);
}
@Override
public boolean isShutdown() {
return getDelegate().isShutdown();
}
@Override
public boolean isTerminated() {
return getDelegate().isTerminated();
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
return getDelegate().invokeAll(tasks);
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
TimeUnit unit) throws InterruptedException {
return getDelegate().invokeAll(tasks, timeout, unit);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException,
ExecutionException {
return getDelegate().invokeAny(tasks);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return getDelegate().invokeAny(tasks, timeout, unit);
}
@Override
public boolean isShuttingDown() {
return getDelegate().isShuttingDown();
}
@Override
public Iterator<EventExecutor> iterator() {
return getDelegate().iterator();
}
@Override
public <V> Future<V> newFailedFuture(Throwable arg0) {
return getDelegate().newFailedFuture(arg0);
}
@Override
public EventLoop next() {
return ((EventLoopGroup) getDelegate()).next();
}
@Override
public <V> ProgressivePromise<V> newProgressivePromise() {
return getDelegate().newProgressivePromise();
}
@Override
public <V> Promise<V> newPromise() {
return getDelegate().newPromise();
}
@Override
public <V> Future<V> newSucceededFuture(V arg0) {
return getDelegate().newSucceededFuture(arg0);
}
@Override
public EventLoopGroup parent() {
return getDelegate().parent();
}
@Override
public ChannelFuture register(Channel arg0, ChannelPromise arg1) {
return getDelegate().register(arg0, arg1);
}
@Override
public ChannelFuture register(Channel arg0) {
return getDelegate().register(arg0);
}
@Override
public Future<?> shutdownGracefully() {
return getDelegate().shutdownGracefully();
}
@Override
public Future<?> shutdownGracefully(long arg0, long arg1, TimeUnit arg2) {
return getDelegate().shutdownGracefully(arg0, arg1, arg2);
}
@Override
public Future<?> terminationFuture() {
return getDelegate().terminationFuture();
}
@Override
@Deprecated
public void shutdown() {
getDelegate().shutdown();
}
@Override
@Deprecated
public List<Runnable> shutdownNow() {
return getDelegate().shutdownNow();
}
}
@@ -0,0 +1,239 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.channel.Channel;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.compat.netty.independent.NettyChannelInjector.ChannelSocketInjector;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.ClosedInjector;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
/**
* Represents an injector factory.
* <p>
* Note that the factory will return {@link ClosedInjector} when the factory is closed.
* @author Kristian
*/
public class NettyInjectionFactory {
// This should work as long as the injectors are, uh, injected
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
// Whether or not the factory is closed
private volatile boolean closed;
// The current plugin
private final Plugin plugin;
public NettyInjectionFactory(Plugin plugin) {
this.plugin = plugin;
}
/**
* Retrieve the main plugin associated with this injection factory.
* @return The main plugin.
*/
public Plugin getPlugin() {
return plugin;
}
/**
* Construct or retrieve a channel injector from an existing Bukkit player.
* @param player - the existing Bukkit player.
* @param listener - the listener.
* @return A new injector, an existing injector associated with this player, or a closed injector.
*/
@Nonnull
public Injector fromPlayer(Player player, ChannelListener listener) {
if (closed)
return new ClosedInjector(player);
Injector injector = playerLookup.get(player);
// Find a temporary injector as well
if (injector == null)
injector = getTemporaryInjector(player);
if (injector != null && !injector.isClosed())
return injector;
Object networkManager = MinecraftFields.getNetworkManager(player);
// Must be a temporary Bukkit player
if (networkManager == null) {
return fromName(player.getName(), player);
}
Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true);
// See if a channel has already been created
injector = (NettyChannelInjector) NettyChannelInjector.findChannelHandler(channel, NettyChannelInjector.class);
if (injector != null) {
// Update the player instance
playerLookup.remove(injector.getPlayer());
injector.setPlayer(player);
} else {
injector = new NettyChannelInjector(player, networkManager, channel, listener, this);
}
// Cache injector and return
cacheInjector(player, injector);
return injector;
}
/**
* Retrieve a cached injector from a name.
* <p>
* The injector may be NULL if the plugin has been reloaded during a player login.
* @param name - the name.
* @param player - the player.
* @return The cached injector, or a closed injector if it could not be found.
*/
public Injector fromName(String name, Player player) {
if (!closed) {
Injector injector = nameLookup.get(name);
// We can only retrieve cached injectors
if (injector != null) {
// Update instance
injector.setUpdatedPlayer(player);
return injector;
}
}
return new ClosedInjector(player);
}
/**
* Construct a new channel injector for the given channel.
* @param channel - the channel.
* @param listener - the listener.
* @param playerFactory - a temporary player creator.
* @return The channel injector, or a closed injector.
*/
@Nonnull
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
if (closed)
return new ClosedInjector(null);
Object networkManager = findNetworkManager(channel);
Player temporaryPlayer = playerFactory.createTemporaryPlayer(Bukkit.getServer());
NettyChannelInjector injector = new NettyChannelInjector(temporaryPlayer, networkManager, channel, listener, this);
// Initialize temporary player
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, new ChannelSocketInjector(injector));
return injector;
}
/**
* Invalidate a cached injector.
* @param player - the associated player.
* @return The cached injector, or NULL if nothing was cached.
*/
public Injector invalidate(Player player) {
Injector injector = playerLookup.remove(player);
nameLookup.remove(player.getName());
return injector;
}
/**
* Cache an injector by player.
* @param player - the player.
* @param injector - the injector to cache.
* @return The previously cached injector.
*/
public Injector cacheInjector(Player player, Injector injector) {
nameLookup.put(player.getName(), injector);
return playerLookup.put(player, injector);
}
/**
* Cache an injector by name alone.
* @param name - the name to lookup.
* @param injector - the injector.
* @return The cached injector.
*/
public Injector cacheInjector(String name, Injector injector) {
return nameLookup.put(name, injector);
}
/**
* Retrieve the associated channel injector.
* @param player - the temporary player, or normal Bukkit player.
* @return The associated injector, or NULL if this is a Bukkit player.
*/
private NettyChannelInjector getTemporaryInjector(Player player) {
SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector != null) {
return ((ChannelSocketInjector) injector).getChannelInjector();
}
return null;
}
/**
* Find the network manager in a channel's pipeline.
* @param channel - the channel.
* @return The network manager.
*/
private Object findNetworkManager(Channel channel) {
// Find the network manager
Object networkManager = NettyChannelInjector.findChannelHandler(channel, MinecraftReflection.getNetworkManagerClass());
if (networkManager != null)
return networkManager;
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
}
/**
* Determine if the factory is closed.
* <p>
* If it is, all new injectors will be closed by default.
* @return TRUE if it is closed, FALSE otherwise.
*/
public boolean isClosed() {
return closed;
}
/**
* Close all injectors created by this factory, and cease the creation of new injections.
*/
public synchronized void close() {
if (!closed) {
closed = true;
// Close everything
for (Injector injector : playerLookup.values())
injector.close();
for (Injector injector : nameLookup.values())
injector.close();
}
}
}
@@ -0,0 +1,371 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.util.concurrent.EventExecutorGroup;
import java.net.SocketAddress;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* A pipeline proxy.
* @author Kristian
*/
public class NettyPipelineProxy implements ChannelPipeline {
protected final ChannelPipeline pipeline;
protected final Channel channel;
public NettyPipelineProxy(ChannelPipeline pipeline, Channel channel) {
this.pipeline = pipeline;
this.channel = channel;
}
@Override
public ChannelPipeline addAfter(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
pipeline.addAfter(arg0, arg1, arg2, arg3);
return this;
}
@Override
public ChannelPipeline addAfter(String arg0, String arg1, ChannelHandler arg2) {
pipeline.addAfter(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addBefore(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
pipeline.addBefore(arg0, arg1, arg2, arg3);
return this;
}
@Override
public ChannelPipeline addBefore(String arg0, String arg1, ChannelHandler arg2) {
pipeline.addBefore(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addFirst(ChannelHandler... arg0) {
pipeline.addFirst(arg0);
return this;
}
@Override
public ChannelPipeline addFirst(EventExecutorGroup arg0, ChannelHandler... arg1) {
pipeline.addFirst(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addFirst(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
pipeline.addFirst(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addFirst(String arg0, ChannelHandler arg1) {
pipeline.addFirst(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addLast(ChannelHandler... arg0) {
pipeline.addLast(arg0);
return this;
}
@Override
public ChannelPipeline addLast(EventExecutorGroup arg0, ChannelHandler... arg1) {
pipeline.addLast(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addLast(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
pipeline.addLast(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addLast(String arg0, ChannelHandler arg1) {
pipeline.addLast(arg0, arg1);
return this;
}
@Override
public ChannelFuture bind(SocketAddress arg0, ChannelPromise arg1) {
return pipeline.bind(arg0, arg1);
}
@Override
public ChannelFuture bind(SocketAddress arg0) {
return pipeline.bind(arg0);
}
@Override
public Channel channel() {
return channel;
}
@Override
public ChannelFuture close() {
return pipeline.close();
}
@Override
public ChannelFuture close(ChannelPromise arg0) {
return pipeline.close(arg0);
}
@Override
public ChannelFuture connect(SocketAddress arg0, ChannelPromise arg1) {
return pipeline.connect(arg0, arg1);
}
@Override
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1, ChannelPromise arg2) {
return pipeline.connect(arg0, arg1, arg2);
}
@Override
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1) {
return pipeline.connect(arg0, arg1);
}
@Override
public ChannelFuture connect(SocketAddress arg0) {
return pipeline.connect(arg0);
}
@Override
public ChannelHandlerContext context(ChannelHandler arg0) {
return pipeline.context(arg0);
}
@Override
public ChannelHandlerContext context(Class<? extends ChannelHandler> arg0) {
return pipeline.context(arg0);
}
@Override
public ChannelHandlerContext context(String arg0) {
return pipeline.context(arg0);
}
// We have to call the depreciated methods to properly implement the proxy
@Override
public ChannelFuture deregister() {
return pipeline.deregister();
}
@Override
public ChannelFuture deregister(ChannelPromise arg0) {
return pipeline.deregister(arg0);
}
@Override
public ChannelPipeline fireChannelUnregistered() {
pipeline.fireChannelUnregistered();
return this;
}
@Override
public ChannelFuture disconnect() {
return pipeline.disconnect();
}
@Override
public ChannelFuture disconnect(ChannelPromise arg0) {
return pipeline.disconnect(arg0);
}
@Override
public ChannelPipeline fireChannelActive() {
pipeline.fireChannelActive();
return this;
}
@Override
public ChannelPipeline fireChannelInactive() {
pipeline.fireChannelInactive();
return this;
}
@Override
public ChannelPipeline fireChannelRead(Object arg0) {
pipeline.fireChannelRead(arg0);
return this;
}
@Override
public ChannelPipeline fireChannelReadComplete() {
pipeline.fireChannelReadComplete();
return this;
}
@Override
public ChannelPipeline fireChannelRegistered() {
pipeline.fireChannelRegistered();
return this;
}
@Override
public ChannelPipeline fireChannelWritabilityChanged() {
pipeline.fireChannelWritabilityChanged();
return this;
}
@Override
public ChannelPipeline fireExceptionCaught(Throwable arg0) {
pipeline.fireExceptionCaught(arg0);
return this;
}
@Override
public ChannelPipeline fireUserEventTriggered(Object arg0) {
pipeline.fireUserEventTriggered(arg0);
return this;
}
@Override
public ChannelHandler first() {
return pipeline.first();
}
@Override
public ChannelHandlerContext firstContext() {
return pipeline.firstContext();
}
@Override
public ChannelPipeline flush() {
pipeline.flush();
return this;
}
@Override
public <T extends ChannelHandler> T get(Class<T> arg0) {
return pipeline.get(arg0);
}
@Override
public ChannelHandler get(String arg0) {
return pipeline.get(arg0);
}
@Override
public Iterator<Entry<String, ChannelHandler>> iterator() {
return pipeline.iterator();
}
@Override
public ChannelHandler last() {
return pipeline.last();
}
@Override
public ChannelHandlerContext lastContext() {
return pipeline.lastContext();
}
@Override
public List<String> names() {
return pipeline.names();
}
@Override
public ChannelPipeline read() {
pipeline.read();
return this;
}
@Override
public ChannelPipeline remove(ChannelHandler arg0) {
pipeline.remove(arg0);
return this;
}
@Override
public <T extends ChannelHandler> T remove(Class<T> arg0) {
return pipeline.remove(arg0);
}
@Override
public ChannelHandler remove(String arg0) {
return pipeline.remove(arg0);
}
@Override
public ChannelHandler removeFirst() {
return pipeline.removeFirst();
}
@Override
public ChannelHandler removeLast() {
return pipeline.removeLast();
}
@Override
public ChannelPipeline replace(ChannelHandler arg0, String arg1, ChannelHandler arg2) {
pipeline.replace(arg0, arg1, arg2);
return this;
}
@Override
public <T extends ChannelHandler> T replace(Class<T> arg0, String arg1, ChannelHandler arg2) {
return pipeline.replace(arg0, arg1, arg2);
}
@Override
public ChannelHandler replace(String arg0, String arg1, ChannelHandler arg2) {
return pipeline.replace(arg0, arg1, arg2);
}
@Override
public Map<String, ChannelHandler> toMap() {
return pipeline.toMap();
}
@Override
public ChannelFuture write(Object arg0, ChannelPromise arg1) {
return pipeline.write(arg0, arg1);
}
@Override
public ChannelFuture write(Object arg0) {
return pipeline.write(arg0);
}
@Override
public ChannelFuture writeAndFlush(Object arg0, ChannelPromise arg1) {
return pipeline.writeAndFlush(arg0, arg1);
}
@Override
public ChannelFuture writeAndFlush(Object arg0) {
return pipeline.writeAndFlush(arg0);
}
}
@@ -0,0 +1,441 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Set;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.compat.netty.ChannelInjector;
import com.comphenix.protocol.compat.netty.ProtocolInjector;
import com.comphenix.protocol.compat.netty.WrappedChannel;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.NettyNetworkMarker;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.injector.spigot.AbstractPacketInjector;
import com.comphenix.protocol.injector.spigot.AbstractPlayerHandler;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Lists;
public class NettyProtocolInjector implements ProtocolInjector {
public static final ReportType REPORT_CANNOT_INJECT_INCOMING_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
private volatile boolean injected;
private volatile boolean closed;
// The temporary player factory
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
private List<VolatileField> bootstrapFields = Lists.newArrayList();
// The channel injector factory
private NettyInjectionFactory injectionFactory;
// List of network managers
private volatile List<Object> networkManagers;
// Different sending filters
private PacketTypeSet sendingFilters = new PacketTypeSet();
private PacketTypeSet reveivedFilters = new PacketTypeSet();
// Packets that must be executed on the main thread
private PacketTypeSet mainThreadFilters = new PacketTypeSet();
// Which packets are buffered
private PacketTypeSet bufferedPackets = new PacketTypeSet();
private ListenerInvoker invoker;
// Handle errors
private ErrorReporter reporter;
private boolean debug;
public NettyProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) {
this.injectionFactory = new NettyInjectionFactory(plugin);
this.invoker = invoker;
this.reporter = reporter;
}
@Override
public boolean isDebug() {
return debug;
}
/**
* Set whether or not the debug mode is enabled.
* @param debug - TRUE if it is, FALSE otherwise.
*/
@Override
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* Inject into the spigot connection class.
*/
@Override
@SuppressWarnings("unchecked")
public synchronized void inject() {
if (injected)
throw new IllegalStateException("Cannot inject twice.");
try {
FuzzyReflection fuzzyServer = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
List<Method> serverConnectionMethods = fuzzyServer.getMethodListByParameters(MinecraftReflection.getServerConnectionClass(), new Class[] {});
// Get the server connection
Object server = fuzzyServer.getSingleton();
Object serverConnection = null;
for (Method method : serverConnectionMethods) {
try {
serverConnection = method.invoke(server);
// Continue until we get a server connection
if (serverConnection != null) {
break;
}
} catch (Exception e) {
// Try the next though
e.printStackTrace();
}
}
// Handle connected channels
final ChannelInboundHandler endInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
try {
// This can take a while, so we need to stop the main thread from interfering
synchronized (networkManagers) {
injectionFactory.fromChannel(channel, NettyProtocolInjector.this, playerFactory).inject();
}
} catch (Exception e) {
reporter.reportDetailed(NettyProtocolInjector.this, Report.newBuilder(REPORT_CANNOT_INJECT_INCOMING_CHANNEL).messageParam(channel).error(e));
}
}
};
// This is executed before Minecraft's channel handler
final ChannelInboundHandler beginInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
// Our only job is to add init protocol
channel.pipeline().addLast(endInitProtocol);
}
};
// Add our handler to newly created channels
final ChannelHandler connectionHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = (Channel) msg;
// Prepare to initialize ths channel
channel.pipeline().addFirst(beginInitProtocol);
ctx.fireChannelRead(msg);
}
};
// Get the current NetworkMananger list
networkManagers = (List<Object>) FuzzyReflection.fromObject(serverConnection, true).invokeMethod(null, "getNetworkManagers", List.class, serverConnection);
// Insert ProtocolLib's connection interceptor
bootstrapFields = getBootstrapFields(serverConnection);
for (VolatileField field : bootstrapFields) {
final List<Object> list = (List<Object>) field.getValue();
// We don't have to override this list
if (list == networkManagers) {
continue;
}
// Synchronize with each list before we attempt to replace them.
field.setValue(new NettyBootstrapList(list, connectionHandler));
}
injected = true;
} catch (Exception e) {
throw new RuntimeException("Unable to inject channel futures.", e);
}
}
@Override
public boolean hasListener(Class<?> packetClass) {
return reveivedFilters.contains(packetClass) || sendingFilters.contains(packetClass);
}
@Override
public boolean hasMainThreadListener(Class<?> packetClass) {
return mainThreadFilters.contains(packetClass);
}
@Override
public ErrorReporter getReporter() {
return reporter;
}
/**
* Inject our packet handling into a specific player.
* @param player Player to inject into
*/
public void injectPlayer(Player player) {
injectionFactory.fromPlayer(player, this).inject();
}
private List<VolatileField> getBootstrapFields(Object serverConnection) {
List<VolatileField> result = Lists.newArrayList();
// Find and (possibly) proxy every list
for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) {
VolatileField volatileField = new VolatileField(field, serverConnection, true).toSynchronized();
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) volatileField.getValue();
if (list.size() == 0 || list.get(0) instanceof ChannelFuture) {
result.add(volatileField);
}
}
return result;
}
/**
* Clean up any remaning injections.
*/
@Override
public synchronized void close() {
if (!closed) {
closed = true;
for (VolatileField field : bootstrapFields) {
Object value = field.getValue();
// Undo the processed channels, if any
if (value instanceof NettyBootstrapList) {
((NettyBootstrapList) value).close();
}
field.revertValue();
}
// Uninject all the players
injectionFactory.close();
}
}
@Override
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) {
Class<?> clazz = packet.getClass();
if (sendingFilters.contains(clazz) || marker != null) {
try {
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
return packetQueued(container, injector.getPlayer(), marker);
} catch (LinkageError e) {
// So far this has been seen when the jar is shared
System.err.println("[ProtocolLib] Encountered a LinkageError. Make sure you\'re not using this jar for multiple server instances!");
System.err.println("[ProtocolLib] If you\'re getting this error for other reasons, please report it!");
e.printStackTrace();
}
}
// Don't change anything
return null;
}
@Override
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) {
Class<?> clazz = packet.getClass();
if (reveivedFilters.contains(clazz) || marker != null) {
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
return packetReceived(container, injector.getPlayer(), marker);
}
// Don't change anything
return null;
}
@Override
public boolean includeBuffer(Class<?> packetClass) {
return bufferedPackets.contains(packetClass);
}
/**
* Called to inform the event listeners of a queued packet.
* @param packet - the packet that is to be sent.
* @param receiver - the receiver of this packet.
* @return The packet event that was used.
*/
private PacketEvent packetQueued(PacketContainer packet, Player receiver, NetworkMarker marker) {
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver);
invoker.invokePacketSending(event);
return event;
}
/**
* Called to inform the event listeners of a received packet.
* @param packet - the packet that has been receieved.
* @param sender - the client packet.
* @param marker - the network marker.
* @return The packet event that was used.
*/
private PacketEvent packetReceived(PacketContainer packet, Player sender, NetworkMarker marker) {
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
invoker.invokePacketRecieving(event);
return event;
}
// Server side
@Override
public PlayerInjectionHandler getPlayerInjector() {
return new AbstractPlayerHandler(sendingFilters) {
private ChannelListener listener = NettyProtocolInjector.this;
@Override
public int getProtocolVersion(Player player) {
return injectionFactory.fromPlayer(player, listener).getProtocolVersion();
}
@Override
public void updatePlayer(Player player) {
injectionFactory.fromPlayer(player, listener).inject();
}
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
injectionFactory.fromPlayer(player, listener).inject();
}
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
// Ignore this too
return true;
}
@Override
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
if (options != null && !type.forceAsync() && !options.contains(ListenerOptions.ASYNC))
mainThreadFilters.addType(type);
super.addPacketHandler(type, options);
}
@Override
public void removePacketHandler(PacketType type) {
mainThreadFilters.removeType(type);
super.removePacketHandler(type);
}
@Override
public boolean uninjectPlayer(Player player) {
// Just let Netty clean this up
return true;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
injectionFactory.fromPlayer(receiver, listener).sendServerPacket(packet.getHandle(), marker, filters);
}
@Override
public boolean hasMainThreadListener(PacketType type) {
return mainThreadFilters.contains(type);
}
@Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
injectionFactory.fromPlayer(player, listener).recieveClientPacket(mcPacket);
}
@Override
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
// Ignore this
return null;
}
@Override
public void handleDisconnect(Player player) {
injectionFactory.fromPlayer(player, listener).close();
}
@Override
public WrappedChannel getChannel(Player player) {
Injector injector = injectionFactory.fromPlayer(player, listener);
if (injector instanceof ChannelInjector) {
return ((ChannelInjector) injector).getChannel();
}
return null;
}
};
}
/**
* Retrieve a view of this protocol injector as a packet injector.
* @return The packet injector.
*/
// Client side
@Override
public PacketInjector getPacketInjector() {
return new AbstractPacketInjector(reveivedFilters) {
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
NetworkMarker marker = buffered != null ? new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null;
injectionFactory.fromPlayer(client, NettyProtocolInjector.this).saveMarker(packet.getHandle(), marker);
return packetReceived(packet, client, marker);
}
@Override
public void inputBuffersChanged(Set<PacketType> set) {
bufferedPackets = new PacketTypeSet(set);
}
};
}
}
@@ -0,0 +1,104 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import java.util.Map;
import java.util.Map.Entry;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.collect.Maps;
/**
* @author dmulloy2
*/
public class NettyProtocolRegistry extends ProtocolRegistry {
@Override
protected synchronized void initialize() {
Object[] protocols = enumProtocol.getEnumConstants();
// ID to Packet class maps
Map<Object, Map<Integer, Class<?>>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Integer, Class<?>>> clientMaps = Maps.newLinkedHashMap();
Register result = new Register();
StructureModifier<Object> modifier = null;
// Iterate through the protocols
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Object, Map<Integer, Class<?>>>> maps = modifier.withTarget(protocol).withType(Map.class);
for (Entry<Object, Map<Integer, Class<?>>> entry : maps.read(0).entrySet()) {
String direction = entry.getKey().toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, entry.getValue());
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, entry.getValue());
}
}
}
// Maps we have to occationally check have changed
for (Map<Integer, Class<?>> map : serverMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Map<Integer, Class<?>> map : clientMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (int i = 0; i < protocols.length; i++) {
Object protocol = protocols[i];
Enum<?> enumProtocol = (Enum<?>) protocol;
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
if (serverMaps.containsKey(protocol))
associatePackets(result, serverMaps.get(protocol), equivalent, Sender.SERVER);
if (clientMaps.containsKey(protocol))
associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
@Override
protected void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
for (Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromCurrent(protocol, sender, entry.getKey(), entry.getValue());
try {
register.typeToClass.put(type, entry.getValue());
if (sender == Sender.SERVER)
register.serverPackets.add(type);
if (sender == Sender.CLIENT)
register.clientPackets.add(type);
} catch (IllegalArgumentException ex) {
// Sometimes this happens with fake packets, just ignore it
}
}
}
}
@@ -0,0 +1,265 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.compat.netty.independent;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.socket.SocketChannel;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
/**
* This class wraps a Netty {@link Channel} in a {@link Socket}. It overrides
* all methods in {@link Socket} to ensure that calls are not mistakingly made
* to the unsupported super socket. All operations that can be sanely applied to
* a {@link Channel} are implemented here. Those which cannot will throw an
* {@link UnsupportedOperationException}.
*/
// Thanks MD5. :)
public class NettySocketAdapter extends Socket {
private final SocketChannel ch;
private NettySocketAdapter(SocketChannel ch) {
this.ch = ch;
}
public static NettySocketAdapter adapt(SocketChannel ch) {
return new NettySocketAdapter(ch);
}
@Override
public void bind(SocketAddress bindpoint) throws IOException {
ch.bind(bindpoint).syncUninterruptibly();
}
@Override
public synchronized void close() throws IOException {
ch.close().syncUninterruptibly();
}
@Override
public void connect(SocketAddress endpoint) throws IOException {
ch.connect(endpoint).syncUninterruptibly();
}
@Override
public void connect(SocketAddress endpoint, int timeout) throws IOException {
ch.config().setConnectTimeoutMillis(timeout);
ch.connect(endpoint).syncUninterruptibly();
}
@Override
public boolean equals(Object obj) {
return obj instanceof NettySocketAdapter && ch.equals(((NettySocketAdapter) obj).ch);
}
@Override
public java.nio.channels.SocketChannel getChannel() {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public InetAddress getInetAddress() {
return ch.remoteAddress().getAddress();
}
@Override
public InputStream getInputStream() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public boolean getKeepAlive() throws SocketException {
return ch.config().getOption(ChannelOption.SO_KEEPALIVE);
}
@Override
public InetAddress getLocalAddress() {
return ch.localAddress().getAddress();
}
@Override
public int getLocalPort() {
return ch.localAddress().getPort();
}
@Override
public SocketAddress getLocalSocketAddress() {
return ch.localAddress();
}
@Override
public boolean getOOBInline() throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public int getPort() {
return ch.remoteAddress().getPort();
}
@Override
public synchronized int getReceiveBufferSize() throws SocketException {
return ch.config().getOption(ChannelOption.SO_RCVBUF);
}
@Override
public SocketAddress getRemoteSocketAddress() {
return ch.remoteAddress();
}
@Override
public boolean getReuseAddress() throws SocketException {
return ch.config().getOption(ChannelOption.SO_REUSEADDR);
}
@Override
public synchronized int getSendBufferSize() throws SocketException {
return ch.config().getOption(ChannelOption.SO_SNDBUF);
}
@Override
public int getSoLinger() throws SocketException {
return ch.config().getOption(ChannelOption.SO_LINGER);
}
@Override
public synchronized int getSoTimeout() throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public boolean getTcpNoDelay() throws SocketException {
return ch.config().getOption(ChannelOption.TCP_NODELAY);
}
@Override
public int getTrafficClass() throws SocketException {
return ch.config().getOption(ChannelOption.IP_TOS);
}
@Override
public int hashCode() {
return ch.hashCode();
}
@Override
public boolean isBound() {
return ch.localAddress() != null;
}
@Override
public boolean isClosed() {
return !ch.isOpen();
}
@Override
public boolean isConnected() {
return ch.isActive();
}
@Override
public boolean isInputShutdown() {
return ch.isInputShutdown();
}
@Override
public boolean isOutputShutdown() {
return ch.isOutputShutdown();
}
@Override
public void sendUrgentData(int data) throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setKeepAlive(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.SO_KEEPALIVE, on);
}
@Override
public void setOOBInline(boolean on) throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public synchronized void setReceiveBufferSize(int size) throws SocketException {
ch.config().setOption(ChannelOption.SO_RCVBUF, size);
}
@Override
public void setReuseAddress(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.SO_REUSEADDR, on);
}
@Override
public synchronized void setSendBufferSize(int size) throws SocketException {
ch.config().setOption(ChannelOption.SO_SNDBUF, size);
}
@Override
public void setSoLinger(boolean on, int linger) throws SocketException {
ch.config().setOption(ChannelOption.SO_LINGER, linger);
}
@Override
public synchronized void setSoTimeout(int timeout) throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setTcpNoDelay(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.TCP_NODELAY, on);
}
@Override
public void setTrafficClass(int tc) throws SocketException {
ch.config().setOption(ChannelOption.IP_TOS, tc);
}
@Override
public void shutdownInput() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void shutdownOutput() throws IOException {
ch.shutdownOutput().syncUninterruptibly();
}
@Override
public String toString() {
return ch.toString();
}
}
@@ -24,6 +24,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.Iterables;
@@ -34,40 +35,39 @@ import com.google.common.collect.Iterables;
* @author Kristian
*/
public abstract class AbstractConcurrentListenerMultimap<TListener> {
// The core of our map
private ConcurrentMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> listeners =
new ConcurrentHashMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
private ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> mapListeners;
public AbstractConcurrentListenerMultimap() {
mapListeners = new ConcurrentHashMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
}
/**
* Adds a listener to its requested list of packet recievers.
* @param listener - listener with a list of packets to recieve notifcations for.
* Adds a listener to its requested list of packet receivers.
* @param listener - listener with a list of packets to receive notifications for.
* @param whitelist - the packet whitelist to use.
*/
public void addListener(TListener listener, ListeningWhitelist whitelist) {
PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
for (Integer packetID : whitelist.getWhitelist()) {
addListener(packetID, prioritized);
for (PacketType type : whitelist.getTypes()) {
addListener(type, prioritized);
}
}
// Add the listener to a specific packet notifcation list
private void addListener(Integer packetID, PrioritizedListener<TListener> listener) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
private void addListener(PacketType type, PrioritizedListener<TListener> listener) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
// We don't want to create this for every lookup
if (list == null) {
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
// which is a essential feature for our purposes.
final SortedCopyOnWriteArray<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>();
list = listeners.putIfAbsent(packetID, value);
// We may end up creating multiple multisets, but we'll agree
// on the one to use.
// We may end up creating multiple multisets, but we'll agree on which to use
list = mapListeners.putIfAbsent(type, value);
if (list == null) {
list = value;
}
@@ -83,14 +83,12 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
* @param whitelist - the packet whitelist that was used.
* @return Every packet ID that was removed due to no listeners.
*/
public List<Integer> removeListener(TListener listener, ListeningWhitelist whitelist) {
List<Integer> removedPackets = new ArrayList<Integer>();
public List<PacketType> removeListener(TListener listener, ListeningWhitelist whitelist) {
List<PacketType> removedPackets = new ArrayList<PacketType>();
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
for (Integer packetID : whitelist.getWhitelist()) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
for (PacketType type : whitelist.getTypes()) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
// Remove any listeners
if (list != null) {
@@ -100,15 +98,13 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
if (list.size() == 0) {
listeners.remove(packetID);
removedPackets.add(packetID);
mapListeners.remove(type);
removedPackets.add(type);
}
}
}
// Move on to the next
}
return removedPackets;
}
@@ -116,11 +112,11 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
* Retrieve the registered listeners, in order from the lowest to the highest priority.
* <p>
* The returned list is thread-safe and doesn't require synchronization.
* @param packetID - packet ID.
* @param type - packet type.
* @return Registered listeners.
*/
public Collection<PrioritizedListener<TListener>> getListener(int packetID) {
return listeners.get(packetID);
public Collection<PrioritizedListener<TListener>> getListener(PacketType type) {
return mapListeners.get(type);
}
/**
@@ -128,21 +124,21 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
* @return Every listener.
*/
public Iterable<PrioritizedListener<TListener>> values() {
return Iterables.concat(listeners.values());
return Iterables.concat(mapListeners.values());
}
/**
* Retrieve every registered packet ID:
* @return Registered packet ID.
* Retrieve every registered packet type:
* @return Registered packet type.
*/
public Set<Integer> keySet() {
return listeners.keySet();
public Set<PacketType> keySet() {
return mapListeners.keySet();
}
/**
* Remove all packet listeners.
*/
protected void clearListeners() {
listeners.clear();
mapListeners.clear();
}
}
@@ -0,0 +1,495 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.concurrency;
import java.util.HashSet;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import com.comphenix.protocol.compat.guava.Guava;
import com.google.common.base.Objects;
import com.google.common.collect.Range;
/**
* Represents a generic store of intervals and associated values. No two intervals
* can overlap in this representation.
* <p>
* Note that this implementation is not thread safe.
*
* @author Kristian
*
* @param <TKey> - type of the key. Must implement Comparable.
* @param <TValue> - type of the value to associate.
*/
public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue> {
protected enum State {
OPEN,
CLOSE,
BOTH
}
/**
* Represents a range and a value in this interval tree.
*/
public class Entry implements Map.Entry<Range<TKey>, TValue> {
private EndPoint left;
private EndPoint right;
Entry(EndPoint left, EndPoint right) {
if (left == null)
throw new IllegalAccessError("left cannot be NUll");
if (right == null)
throw new IllegalAccessError("right cannot be NUll");
if (left.key.compareTo(right.key) > 0)
throw new IllegalArgumentException(
"Left key (" + left.key + ") cannot be greater than the right key (" + right.key + ")");
this.left = left;
this.right = right;
}
@Override
public Range<TKey> getKey() {
return Guava.closedRange(left.key, right.key);
}
@Override
public TValue getValue() {
return left.value;
}
@Override
public TValue setValue(TValue value) {
TValue old = left.value;
// Set both end points
left.value = value;
right.value = value;
return old;
}
@SuppressWarnings("rawtypes")
@Override
public boolean equals(Object obj) {
// Quick equality check
if (obj == this) {
return true;
} else if (obj instanceof AbstractIntervalTree.Entry) {
return Objects.equal(left.key, ((AbstractIntervalTree.Entry) obj).left.key) &&
Objects.equal(right.key, ((AbstractIntervalTree.Entry) obj).right.key) &&
Objects.equal(left.value, ((AbstractIntervalTree.Entry) obj).left.value);
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hashCode(left.key, right.key, left.value);
}
@Override
public String toString() {
return String.format("Value %s at [%s, %s]", left.value, left.key, right.key);
}
}
/**
* Represents a single end point (open, close or both) of a range.
*/
protected class EndPoint {
// Whether or not the end-point is opening a range, closing a range or both.
public State state;
// The value this range contains
public TValue value;
// The key of this end point
public TKey key;
public EndPoint(State state, TKey key, TValue value) {
this.state = state;
this.key = key;
this.value = value;
}
}
// To quickly look up ranges we'll index them by endpoints
protected NavigableMap<TKey, EndPoint> bounds = new TreeMap<TKey, EndPoint>();
/**
* Removes every interval that intersects with the given range.
* @param lowerBound - lowest value to remove.
* @param upperBound - highest value to remove.
* @return Intervals that were removed
*/
public Set<Entry> remove(TKey lowerBound, TKey upperBound) {
return remove(lowerBound, upperBound, false);
}
/**
* Removes every interval that intersects with the given range.
* @param lowerBound - lowest value to remove.
* @param upperBound - highest value to remove.
* @param preserveDifference - whether or not to preserve the intervals that are partially outside.
* @return Intervals that were removed
*/
public Set<Entry> remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
checkBounds(lowerBound, upperBound);
NavigableMap<TKey, EndPoint> range = bounds.subMap(lowerBound, true, upperBound, true);
EndPoint first = getNextEndPoint(lowerBound, true);
EndPoint last = getPreviousEndPoint(upperBound, true);
// Used while resizing intervals
EndPoint previous = null;
EndPoint next = null;
Set<Entry> resized = new HashSet<Entry>();
Set<Entry> removed = new HashSet<Entry>();
// Remove the previous element too. A close end-point must be preceded by an OPEN end-point.
if (first != null && first.state == State.CLOSE) {
previous = getPreviousEndPoint(first.key, false);
// Add the interval back
if (previous != null) {
removed.add(getEntry(previous, first));
}
}
// Get the closing element too.
if (last != null && last.state == State.OPEN) {
next = getNextEndPoint(last.key, false);
if (next != null) {
removed.add(getEntry(last, next));
}
}
// Now remove both ranges
removeEntrySafely(previous, first);
removeEntrySafely(last, next);
// Add new resized intervals
if (preserveDifference) {
if (previous != null) {
resized.add(putUnsafe(previous.key, decrementKey(lowerBound), previous.value));
}
if (next != null) {
resized.add(putUnsafe(incrementKey(upperBound), next.key, next.value));
}
}
// Get the removed entries too
getEntries(removed, range);
invokeEntryRemoved(removed);
if (preserveDifference) {
invokeEntryAdded(resized);
}
// Remove the range as well
range.clear();
return removed;
}
/**
* Retrieve the entry from a given set of end points.
* @param left - leftmost end point.
* @param right - rightmost end point.
* @return The associated entry.
*/
protected Entry getEntry(EndPoint left, EndPoint right) {
if (left == null)
throw new IllegalArgumentException("left endpoint cannot be NULL.");
if (right == null)
throw new IllegalArgumentException("right endpoint cannot be NULL.");
// Make sure the order is correct
if (right.key.compareTo(left.key) < 0) {
return getEntry(right, left);
} else {
return new Entry(left, right);
}
}
private void removeEntrySafely(EndPoint left, EndPoint right) {
if (left != null && right != null) {
bounds.remove(left.key);
bounds.remove(right.key);
}
}
// Adds a given end point
protected EndPoint addEndPoint(TKey key, TValue value, State state) {
EndPoint endPoint = bounds.get(key);
if (endPoint != null) {
endPoint.state = State.BOTH;
} else {
endPoint = new EndPoint(state, key, value);
bounds.put(key, endPoint);
}
return endPoint;
}
/**
* Associates a given interval of keys with a certain value. Any previous
* association will be overwritten in the given interval.
* <p>
* Overlapping intervals are not permitted. A key can only be associated with a single value.
*
* @param lowerBound - the minimum key (inclusive).
* @param upperBound - the maximum key (inclusive).
* @param value - the value, or NULL to reset this range.
*/
public void put(TKey lowerBound, TKey upperBound, TValue value) {
// While we don't permit overlapping intervals, we'll still allow overwriting existing intervals.
remove(lowerBound, upperBound, true);
invokeEntryAdded(putUnsafe(lowerBound, upperBound, value));
}
/**
* Associates a given interval without performing any interval checks.
* @param lowerBound - the minimum key (inclusive).
* @param upperBound - the maximum key (inclusive).
* @param value - the value, or NULL to reset the range.
*/
private Entry putUnsafe(TKey lowerBound, TKey upperBound, TValue value) {
// OK. Add the end points now
if (value != null) {
EndPoint left = addEndPoint(lowerBound, value, State.OPEN);
EndPoint right = addEndPoint(upperBound, value, State.CLOSE);
return new Entry(left, right);
} else {
return null;
}
}
/**
* Used to verify the validity of the given interval.
* @param lowerBound - lower bound (inclusive).
* @param upperBound - upper bound (inclusive).
*/
private void checkBounds(TKey lowerBound, TKey upperBound) {
if (lowerBound == null)
throw new IllegalAccessError("lowerbound cannot be NULL.");
if (upperBound == null)
throw new IllegalAccessError("upperBound cannot be NULL.");
if (upperBound.compareTo(lowerBound) < 0)
throw new IllegalArgumentException("upperBound cannot be less than lowerBound.");
}
/**
* Determines if the given key is within an interval.
* @param key - key to check.
* @return TRUE if the given key is within an interval in this tree, FALSE otherwise.
*/
public boolean containsKey(TKey key) {
return getEndPoint(key) != null;
}
/**
* Enumerates over every range in this interval tree.
* @return Number of ranges.
*/
public Set<Entry> entrySet() {
// Don't mind the Java noise
Set<Entry> result = new HashSet<Entry>();
getEntries(result, bounds);
return result;
}
/**
* Remove every interval.
*/
public void clear() {
if (!bounds.isEmpty()) {
remove(bounds.firstKey(), bounds.lastKey());
}
}
/**
* Converts a map of end points into a set of entries.
* @param destination - set of entries.
* @param map - a map of end points.
*/
private void getEntries(Set<Entry> destination, NavigableMap<TKey, EndPoint> map) {
Map.Entry<TKey, EndPoint> last = null;
for (Map.Entry<TKey, EndPoint> entry : map.entrySet()) {
switch (entry.getValue().state) {
case BOTH:
EndPoint point = entry.getValue();
destination.add(new Entry(point, point));
break;
case CLOSE:
if (last != null) {
destination.add(new Entry(last.getValue(), entry.getValue()));
}
break;
case OPEN:
// We don't know the full range yet
last = entry;
break;
default:
throw new IllegalStateException("Illegal open/close state detected.");
}
}
}
/**
* Inserts every range from the given tree into the current tree.
* @param other - the other tree to read from.
*/
public void putAll(AbstractIntervalTree<TKey, TValue> other) {
// Naively copy every range.
for (Entry entry : other.entrySet()) {
put(entry.left.key, entry.right.key, entry.getValue());
}
}
/**
* Retrieves the value of the range that matches the given key, or NULL if nothing was found.
* @param key - the level to read for.
* @return The correct amount of experience, or NULL if nothing was recorded.
*/
public TValue get(TKey key) {
EndPoint point = getEndPoint(key);
if (point != null)
return point.value;
else
return null;
}
/**
* Get the left-most end-point associated with this key.
* @param key - key to search for.
* @return The end point found, or NULL.
*/
protected EndPoint getEndPoint(TKey key) {
EndPoint ends = bounds.get(key);
if (ends != null) {
// Always return the end point to the left
if (ends.state == State.CLOSE) {
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(decrementKey(key));
return left != null ? left.getValue() : null;
} else {
return ends;
}
} else {
// We need to determine if the point intersects with a range
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(key);
// We only need to check to the left
if (left != null && left.getValue().state == State.OPEN) {
return left.getValue();
} else {
return null;
}
}
}
/**
* Get the previous end point of a given key.
* @param point - the point to search with.
* @param inclusive - whether or not to include the current point in the search.
* @return The previous end point of a given given key, or NULL if not found.
*/
protected EndPoint getPreviousEndPoint(TKey point, boolean inclusive) {
if (point != null) {
Map.Entry<TKey, EndPoint> previous = bounds.floorEntry(inclusive ? point : decrementKey(point));
if (previous != null)
return previous.getValue();
}
return null;
}
/**
* Get the next end point of a given key.
* @param point - the point to search with.
* @param inclusive - whether or not to include the current point in the search.
* @return The next end point of a given given key, or NULL if not found.
*/
protected EndPoint getNextEndPoint(TKey point, boolean inclusive) {
if (point != null) {
Map.Entry<TKey, EndPoint> next = bounds.ceilingEntry(inclusive ? point : incrementKey(point));
if (next != null)
return next.getValue();
}
return null;
}
private void invokeEntryAdded(Entry added) {
if (added != null) {
onEntryAdded(added);
}
}
private void invokeEntryAdded(Set<Entry> added) {
for (Entry entry : added) {
onEntryAdded(entry);
}
}
private void invokeEntryRemoved(Set<Entry> removed) {
for (Entry entry : removed) {
onEntryRemoved(entry);
}
}
// Listeners for added or removed entries
/**
* Invoked when an entry is added.
* @param added - the entry that was added.
*/
protected void onEntryAdded(Entry added) { }
/**
* Invoked when an entry is removed.
* @param removed - the removed entry.
*/
protected void onEntryRemoved(Entry removed) { }
// Helpers for decrementing or incrementing key values
/**
* Decrement the given key by one unit.
* @param key - the key that should be decremented.
* @return The new decremented key.
*/
protected abstract TKey decrementKey(TKey key);
/**
* Increment the given key by one unit.
* @param key - the key that should be incremented.
* @return The new incremented key.
*/
protected abstract TKey incrementKey(TKey key);
}
@@ -0,0 +1,266 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.concurrency;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import com.comphenix.protocol.utility.SafeCacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
/**
* A map that supports blocking on read operations. Null keys are not supported.
* <p>
* Values are stored as weak references, and will be automatically removed once they've all been dereferenced.
* <p>
* @author Kristian
*
* @param <TKey> - type of the key.
* @param <TValue> - type of the value.
*/
public class BlockingHashMap<TKey, TValue> {
// Map of values
private final ConcurrentMap<TKey, TValue> backingMap;
// Map of locked objects
private final ConcurrentMap<TKey, Object> locks;
/**
* Retrieve a cache loader that will always throw an exception.
* @param <TKey> Type of the key
* @param <TValue> Type of the value
* @return An invalid cache loader.
*/
public static <TKey, TValue> CacheLoader<TKey, TValue> newInvalidCacheLoader() {
return new CacheLoader<TKey, TValue>() {
@Override
public TValue load(TKey key) throws Exception {
throw new IllegalStateException("Illegal use. Access the map directly instead.");
}
};
}
/**
* Initialize a new map.
*/
public BlockingHashMap() {
backingMap = SafeCacheBuilder.<TKey, TValue>newBuilder().
weakValues().
removalListener(
new RemovalListener<TKey, TValue>() {
@Override
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
// Clean up locks too
if (entry.getCause() != RemovalCause.REPLACED) {
locks.remove(entry.getKey());
}
}
}).
build(BlockingHashMap.<TKey, TValue> newInvalidCacheLoader());
// Normal concurrent hash map
locks = new ConcurrentHashMap<TKey, Object>();
}
/**
* Initialize a new map.
* @param <TKey> Type of the key
* @param <TValue> Type of the value
* @return The created map.
*/
public static <TKey, TValue> BlockingHashMap<TKey, TValue> create() {
return new BlockingHashMap<TKey, TValue>();
}
/**
* Waits until a value has been associated with the given key, and then retrieves that value.
* @param key - the key whose associated value is to be returned
* @return The value to which the specified key is mapped.
* @throws InterruptedException If the current thread got interrupted while waiting.
*/
public TValue get(TKey key) throws InterruptedException {
if (key == null)
throw new IllegalArgumentException("key cannot be NULL.");
TValue value = backingMap.get(key);
// Only lock if no value is available
if (value == null) {
final Object lock = getLock(key);
synchronized (lock) {
while (value == null) {
lock.wait();
value = backingMap.get(key);
}
}
}
return value;
}
/**
* Waits until a value has been associated with the given key, and then retrieves that value.
* @param key - the key whose associated value is to be returned
* @param timeout - the amount of time to wait until an association has been made.
* @param unit - unit of timeout.
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
* @throws InterruptedException If the current thread got interrupted while waiting.
*/
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
return get(key, timeout, unit, false);
}
/**
* Waits until a value has been associated with the given key, and then retrieves that value.
* <p>
* If timeout is zero, this method will return immediately if it can't find an socket injector.
*
* @param key - the key whose associated value is to be returned
* @param timeout - the amount of time to wait until an association has been made.
* @param unit - unit of timeout.
* @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise.
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
* @throws InterruptedException If the current thread got interrupted while waiting.
*/
public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException {
if (key == null)
throw new IllegalArgumentException("key cannot be NULL.");
if (unit == null)
throw new IllegalArgumentException("Unit cannot be NULL.");
if (timeout < 0)
throw new IllegalArgumentException("Timeout cannot be less than zero.");
TValue value = backingMap.get(key);
// Only lock if no value is available
if (value == null && timeout > 0) {
final Object lock = getLock(key);
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
// Don't exceed the timeout
synchronized (lock) {
while (value == null) {
try {
long remainingTime = stopTimeNS - System.nanoTime();
if (remainingTime > 0) {
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
value = backingMap.get(key);
} else {
// Timeout elapsed
break;
}
} catch (InterruptedException e) {
// This is fairly dangerous - but we might HAVE to block the thread
if (!ignoreInterrupted)
throw e;
}
}
}
}
return value;
}
/**
* Associate a given key with the given value.
* <p>
* Wakes up any blocking getters on this specific key.
*
* @param key - the key to associate.
* @param value - the value.
* @return The previously associated value.
*/
public TValue put(TKey key, TValue value) {
if (value == null)
throw new IllegalArgumentException("This map doesn't support NULL values.");
final TValue previous = backingMap.put(key, value);
final Object lock = getLock(key);
// Inform our readers about this change
synchronized (lock) {
lock.notifyAll();
return previous;
}
}
/**
* If and only if a key is not present in the map will it be associated with the given value.
* @param key - the key to associate.
* @param value - the value to associate.
* @return The previous value this key has been associated with.
*/
public TValue putIfAbsent(TKey key, TValue value) {
if (value == null)
throw new IllegalArgumentException("This map doesn't support NULL values.");
final TValue previous = backingMap.putIfAbsent(key, value);
// No need to unlock readers if we haven't changed anything
if (previous == null) {
final Object lock = getLock(key);
synchronized (lock) {
lock.notifyAll();
}
}
return previous;
}
public int size() {
return backingMap.size();
}
public Collection<TValue> values() {
return backingMap.values();
}
public Set<TKey> keys() {
return backingMap.keySet();
}
/**
* Atomically retrieve the lock associated with a given key.
* @param key - the current key.
* @return An asssociated lock.
*/
private Object getLock(TKey key) {
Object lock = locks.get(key);
if (lock == null) {
Object created = new Object();
// Do this atomically
lock = locks.putIfAbsent(key, created);
// If we succeeded, use the latch we created - otherwise, use the already inserted latch
if (lock == null) {
lock = created;
}
}
return lock;
}
}
@@ -0,0 +1,332 @@
package com.comphenix.protocol.concurrency;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.bukkit.entity.Player;
import com.comphenix.protocol.utility.SafeCacheBuilder;
import com.comphenix.protocol.utility.Util;
import com.google.common.base.Function;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.UncheckedExecutionException;
/**
* Represents a concurrent player map.
* <p>
* This map may use player addresses as keys.
* @author Kristian
*/
public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> implements ConcurrentMap<Player, TValue> {
/**
* Represents the different standard player keys,
* @author Kristian
*/
public enum PlayerKey implements Function<Player, Object> {
/**
* Use a player's {@link Player#getAddress()} as key in the map.
*/
ADDRESS {
@Override
public Object apply(Player player) {
return player.getAddress();
}
},
/**
* Use a player's {@link Player#getName()} as key in the map.
*/
NAME {
@Override
public Object apply(Player player) {
return player.getName();
}
},
}
/**
* An internal map of player keys to values.
*/
protected ConcurrentMap<Object, TValue> valueLookup = createValueMap();
/**
* A cache of the associated keys for each player.
*/
protected ConcurrentMap<Object, Player> keyLookup = createKeyCache();
/**
* The method used to retrieve a unique key for a player.
*/
protected final Function<Player, Object> keyMethod;
/**
* Construct a new concurrent player map that uses each player's address as key.
* @param <T> Parameter type
* @return Concurrent player map.
*/
public static <T> ConcurrentPlayerMap<T> usingAddress() {
return new ConcurrentPlayerMap<T>(PlayerKey.ADDRESS);
}
/**
* Construct a new concurrent player map that uses each player's name as key.
* @param <T> Parameter type
* @return Concurrent player map.
*/
public static <T> ConcurrentPlayerMap<T> usingName() {
return new ConcurrentPlayerMap<T>(PlayerKey.NAME);
}
/**
* Construct a new concurrent player map using the given standard key method.
* @param standardMethod - the standard key method.
*/
public ConcurrentPlayerMap(PlayerKey standardMethod) {
this.keyMethod = standardMethod;
}
/**
* Construct a new concurrent player map using the given custom key method.
* @param method - custom key method.
*/
public ConcurrentPlayerMap(Function<Player, Object> method) {
this.keyMethod = method;
}
/**
* Construct the map that will store the associated values.
* <p>
* The default implementation uses a {@link ConcurrentHashMap}.
* @return The value map.
*/
protected ConcurrentMap<Object, TValue> createValueMap() {
return Maps.newConcurrentMap();
}
/**
* Construct a cache of keys and the associated player.
* @return The key map.
*/
protected ConcurrentMap<Object, Player> createKeyCache() {
return SafeCacheBuilder.newBuilder().
weakValues().
removalListener(
new RemovalListener<Object, Player>() {
@Override
public void onRemoval(RemovalNotification<Object, Player> removed) {
// We ignore explicit removal
if (removed.wasEvicted()) {
onCacheEvicted(removed.getKey());
}
}
}).
build(
new CacheLoader<Object, Player>() {
@Override
public Player load(Object key) throws Exception {
Player player = findOnlinePlayer(key);
if (player != null)
return player;
// Per the contract, this method should not return NULL
throw new IllegalArgumentException(
"Unable to find a player associated with: " + key);
}
});
}
/**
* Invoked when an entry in the cache has been evicted, typically by the garbage collector.
* @param key - the key.
* @param player - the value that was evicted or collected.
*/
private void onCacheEvicted(Object key) {
Player newPlayer = findOnlinePlayer(key);
if (newPlayer != null) {
// Update the reference
keyLookup.put(key, newPlayer);
} else {
valueLookup.remove(key);
}
}
/**
* Find an online player from the given key.
* @param key - a non-null key.
* @return The player with the given key, or NULL if not found.
*/
protected Player findOnlinePlayer(Object key) {
for (Player player : Util.getOnlinePlayers()) {
if (key.equals(keyMethod.apply(player))) {
return player;
}
}
return null;
}
/**
* Lookup a player by key in the cache, optionally searching every online player.
* @param key - the key of the player we are locating.
* @return The player, or NULL if not found.
*/
protected Player lookupPlayer(Object key) {
try {
return keyLookup.get(key);
} catch (UncheckedExecutionException e) {
return null;
}
}
/**
* Retrieve the key of a particular player, ensuring it is cached.
* @param player - the player whose key we want to retrieve.
* @return The key.
*/
protected Object cachePlayerKey(Player player) {
Object key = keyMethod.apply(player);
keyLookup.put(key, player);
return key;
}
@Override
public TValue put(Player key, TValue value) {
return valueLookup.put(cachePlayerKey(key), value);
}
@Override
public TValue putIfAbsent(Player key, TValue value) {
return valueLookup.putIfAbsent(cachePlayerKey(key), value);
}
@Override
public TValue replace(Player key, TValue value) {
return valueLookup.replace(cachePlayerKey(key), value);
}
@Override
public boolean replace(Player key, TValue oldValue, TValue newValue) {
return valueLookup.replace(cachePlayerKey(key), oldValue, newValue);
}
@Override
public TValue remove(Object key) {
if (key instanceof Player) {
Object playerKey = keyMethod.apply((Player) key);
if (playerKey != null) {
TValue value = valueLookup.remove(playerKey);
keyLookup.remove(playerKey);
return value;
}
}
return null;
}
@Override
public boolean remove(Object key, Object value) {
if (key instanceof Player) {
Object playerKey = keyMethod.apply((Player) key);
if (playerKey != null && valueLookup.remove(playerKey, value)) {
keyLookup.remove(playerKey);
return true;
}
}
return false;
}
@Override
public TValue get(Object key) {
if (key instanceof Player) {
Object playerKey = keyMethod.apply((Player) key);
return playerKey != null ? valueLookup.get(playerKey) : null;
}
return null;
}
@Override
public boolean containsKey(Object key) {
if (key instanceof Player) {
Object playerKey = keyMethod.apply((Player) key);
return playerKey != null && valueLookup.containsKey(playerKey);
}
return false;
}
@Override
public Set<Entry<Player, TValue>> entrySet() {
return new AbstractSet<Entry<Player,TValue>>() {
@Override
public Iterator<Entry<Player, TValue>> iterator() {
return entryIterator();
}
@Override
public int size() {
return valueLookup.size();
}
@Override
public void clear() {
valueLookup.clear();
keyLookup.clear();
}
};
}
/**
* Retrieve an iterator of entries that supports removal of elements.
* @return Entry iterator.
*/
private Iterator<Entry<Player, TValue>> entryIterator() {
// Skip entries with stale data
final Iterator<Entry<Object, TValue>> source = valueLookup.entrySet().iterator();
final AbstractIterator<Entry<Player,TValue>> filtered =
new AbstractIterator<Entry<Player,TValue>>() {
@Override
protected Entry<Player, TValue> computeNext() {
while (source.hasNext()) {
Entry<Object, TValue> entry = source.next();
Player player = lookupPlayer(entry.getKey());
if (player == null) {
// Remove entries that cannot be found
source.remove();
keyLookup.remove(entry.getKey());
} else {
return new SimpleEntry<Player, TValue>(player, entry.getValue());
}
}
return endOfData();
}
};
// We can't return AbstractIterator directly, as it doesn't permitt the remove() method
return new Iterator<Entry<Player, TValue>>() {
@Override
public boolean hasNext() {
return filtered.hasNext();
}
@Override
public Entry<Player, TValue> next() {
return filtered.next();
}
@Override
public void remove() {
source.remove();
}
};
}
}
@@ -0,0 +1,116 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.concurrency;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Represents a very quick integer set that uses a lookup table to store membership.
* <p>
* This class is intentionally missing a size method.
* @author Kristian
*/
public class IntegerSet {
private final boolean[] array;
/**
* Initialize a lookup table with the given maximum number of elements.
* <p>
* This creates a set for elements in the range [0, count).
* <p>
* Formally, the current set must be a subset of [0, 1, 2, ..., count - 1].
* @param maximumCount - maximum element value and count.
*/
public IntegerSet(int maximumCount) {
this.array = new boolean[maximumCount];
}
/**
* Initialize a lookup table with a given maximum and value list.
* <p>
* The provided elements must be in the range [0, count).
* @param maximumCount - the maximum element value and count.
* @param values - the elements to add to the set.
*/
public IntegerSet(int maximumCount, Collection<Integer> values) {
this.array = new boolean[maximumCount];
addAll(values);
}
/**
* Determine whether or not the given element exists in the set.
* @param element - the element to check. Must be in the range [0, count).
* @return TRUE if the given element exists, FALSE otherwise.
*/
public boolean contains(int element) {
return array[element];
}
/**
* Add the given element to the set, or do nothing if it already exists.
* @param element - element to add.
* @throws ArrayIndexOutOfBoundsException If the given element is not in the range [0, count).
*/
public void add(int element) {
array[element] = true;
}
/**
* Add the given collection of elements to the set.
* @param packets - elements to add.
*/
public void addAll(Collection<Integer> packets) {
for (Integer id : packets) {
add(id);
}
}
/**
* Remove the given element from the set, or do nothing if it's already removed.
* @param element - element to remove.
*/
public void remove(int element) {
// We don't actually care if the caller tries to remove an element outside the valid set
if (element >= 0 && element < array.length)
array[element] = false;
}
/**
* Remove every element from the set.
*/
public void clear() {
Arrays.fill(array, false);
}
/**
* Convert the current IntegerSet to an equivalent HashSet.
* @return The resulting HashSet.
*/
public Set<Integer> toSet() {
Set<Integer> elements = new HashSet<Integer>();
for (int i = 0; i < array.length; i++) {
if (array[i])
elements.add(i);
}
return elements;
}
}
@@ -0,0 +1,134 @@
package com.comphenix.protocol.concurrency;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
/**
* Represents a concurrent set of packet types.
* @author Kristian
*/
public class PacketTypeSet {
private Set<PacketType> types = Collections.newSetFromMap(Maps.<PacketType, Boolean>newConcurrentMap());
private Set<Class<?>> classes = Collections.newSetFromMap(Maps.<Class<?>, Boolean>newConcurrentMap());
public PacketTypeSet() {
// Do nothing
}
public PacketTypeSet(Collection<? extends PacketType> values) {
for (PacketType type : values) {
addType(type);
}
}
/**
* Add a particular type to the set.
* @param type - the type to add.
*/
public synchronized void addType(PacketType type) {
Class<?> packetClass = getPacketClass(type);
types.add(Preconditions.checkNotNull(type, "type cannot be NULL."));
if (packetClass != null) {
classes.add(getPacketClass(type));
}
}
/**
* Add the given types to the set of packet types.
* @param types - the types to add.
*/
public synchronized void addAll(Iterable<? extends PacketType> types) {
for (PacketType type : types) {
addType(type);
}
}
/**
* Remove a particular type to the set.
* @param type - the type to remove.
*/
public synchronized void removeType(PacketType type) {
Class<?> packetClass = getPacketClass(type);
types.remove(Preconditions.checkNotNull(type, "type cannot be NULL."));
if (packetClass != null) {
classes.remove(getPacketClass(type));
}
}
/**
* Remove the given types from the set.
* @param types Types to remove
*/
public synchronized void removeAll(Iterable<? extends PacketType> types) {
for (PacketType type : types) {
removeType(type);
}
}
/**
* Retrieve the packet class associated with a particular type.
* @param type - the packet type.
* @return The associated packet type.
*/
protected Class<?> getPacketClass(PacketType type) {
return PacketRegistry.getPacketClassFromType(type);
}
/**
* Determine if the given packet type exists in the set.
* @param type - the type to find.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean contains(PacketType type) {
return types.contains(type);
}
/**
* Determine if a packet type with the given packet class exists in the set.
* @param packetClass - the class to find.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean contains(Class<?> packetClass) {
return classes.contains(packetClass);
}
/**
* Determine if the type of a packet is in the current set.
* @param packet - the packet.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean containsPacket(Object packet) {
if (packet == null)
return false;
return classes.contains(packet.getClass());
}
/**
* Retrieve a view of this packet type set.
* @return The packet type values.
*/
public Set<PacketType> values() {
return types;
}
/**
* Retrieve the number of entries in the set.
* @return The number of entries.
*/
public int size() {
return types.size();
}
public synchronized void clear() {
types.clear();
classes.clear();
}
}
@@ -30,7 +30,7 @@ import com.google.common.collect.Iterables;
* An implicitly sorted array list that preserves insertion order and maintains duplicates.
* @param <T> - type of the elements in the list.
*/
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Iterable<T>, Collection<T> {
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Collection<T> {
// Prevent reordering
private volatile List<T> list;
@@ -68,7 +68,6 @@ public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Iterable
*/
@Override
public synchronized boolean add(T value) {
// We use NULL as a special marker, so we don't allow it
if (value == null)
throw new IllegalArgumentException("value cannot be NULL");
@@ -94,7 +93,6 @@ public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Iterable
@Override
public synchronized boolean addAll(Collection<? extends T> values) {
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
@@ -120,20 +118,22 @@ public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Iterable
@Override
public synchronized boolean remove(Object value) {
List<T> copy = new ArrayList<T>();
boolean result = false;
// Note that there's not much to be gained from using BinarySearch, as we
// have to copy (and thus read) the entire list regardless.
// Copy every element except the one given to us.
for (T element : list) {
if (value != null && !Objects.equal(value, element)) {
copy.add(element);
value = null;
if (!Objects.equal(value, element)) {
copy.add(element);
} else {
result = true;
}
}
list = copy;
return value == null;
return result;
}
@Override
@@ -175,7 +175,6 @@ public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Iterable
* @param index - index of the element to remove.
*/
public synchronized void remove(int index) {
List<T> copy = new ArrayList<T>(list);
copy.remove(index);
@@ -237,4 +236,9 @@ public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Iterable
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public String toString() {
return list.toString();
}
}
@@ -0,0 +1,107 @@
package com.comphenix.protocol.error;
import java.io.PrintStream;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import com.comphenix.protocol.reflect.PrettyPrinter;
/**
* Represents a basic error reporter that prints error reports to the standard error stream.
* <p>
* Note that this implementation doesn't distinguish between {@link #reportWarning(Object, Report)}
* and {@link #reportDetailed(Object, Report)} - they both have the exact same behavior.
* @author Kristian
*/
public class BasicErrorReporter implements ErrorReporter {
private final PrintStream output;
/**
* Construct a new basic error reporter that prints directly the standard error stream.
*/
public BasicErrorReporter() {
this(System.err);
}
/**
* Construct a error reporter that prints to the given output stream.
* @param output - the output stream.
*/
public BasicErrorReporter(PrintStream output) {
this.output = output;
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
output.println("Unhandled exception occured in " + methodName + " for " + sender.getName());
error.printStackTrace(output);
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
reportMinimal(sender, methodName, error);
// Also print parameters
printParameters(parameters);
}
@Override
public void reportDebug(Object sender, Report report) {
// We just have to swallow it
}
@Override
public void reportDebug(Object sender, ReportBuilder builder) {
// As above
}
@Override
public void reportWarning(Object sender, Report report) {
// Basic warning
output.println("[" + sender.getClass().getSimpleName() + "] " + report.getReportMessage());
if (report.getException() != null) {
report.getException().printStackTrace(output);
}
printParameters(report.getCallerParameters());
}
@Override
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
reportWarning(sender, reportBuilder.build());
}
@Override
public void reportDetailed(Object sender, Report report) {
// No difference from warning
reportWarning(sender, report);
}
@Override
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
reportWarning(sender, reportBuilder);
}
/**
* Print the given parameters to the standard error stream.
* @param parameters - the output parameters.
*/
private void printParameters(Object[] parameters) {
if (parameters != null && parameters.length > 0) {
output.println("Parameters: ");
try {
for (Object parameter : parameters) {
if (parameter == null)
output.println("[NULL]");
else
output.println(PrettyPrinter.printObject(parameter));
}
} catch (IllegalAccessException e) {
// Damn it
e.printStackTrace();
}
}
}
}
@@ -0,0 +1,94 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
/**
* Construct an error reporter that delegates to another error reporter.
* @author Kristian
*/
public class DelegatedErrorReporter implements ErrorReporter {
private final ErrorReporter delegated;
/**
* Construct a new error reporter that forwards all reports to a given reporter.
* @param delegated - the delegated reporter.
*/
public DelegatedErrorReporter(ErrorReporter delegated) {
this.delegated = delegated;
}
/**
* Retrieve the underlying error reporter.
* @return Underlying error reporter.
*/
public ErrorReporter getDelegated() {
return delegated;
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
delegated.reportMinimal(sender, methodName, error);
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
delegated.reportMinimal(sender, methodName, error, parameters);
}
@Override
public void reportDebug(Object sender, Report report) {
Report transformed = filterReport(sender, report, false);
if (transformed != null) {
delegated.reportDebug(sender, transformed);
}
}
@Override
public void reportWarning(Object sender, Report report) {
Report transformed = filterReport(sender, report, false);
if (transformed != null) {
delegated.reportWarning(sender, transformed);
}
}
@Override
public void reportDetailed(Object sender, Report report) {
Report transformed = filterReport(sender, report, true);
if (transformed != null) {
delegated.reportDetailed(sender, transformed);
}
}
/**
* Invoked before an error report is passed on to the underlying error reporter.
* <p>
* To cancel a report, return NULL.
* @param sender - the sender instance or class.
* @param report - the error report.
* @param detailed - whether or not the report will be displayed in detail.
* @return The report to pass on, or NULL to cancel it.
*/
protected Report filterReport(Object sender, Report report, boolean detailed) {
return report;
}
@Override
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
reportWarning(sender, reportBuilder.build());
}
@Override
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
reportDetailed(sender, reportBuilder.build());
}
@Override
public void reportDebug(Object sender, ReportBuilder builder) {
reportDebug(sender, builder.build());
}
}
@@ -0,0 +1,616 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.error;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.collections.ExpireHashMap;
import com.comphenix.protocol.error.Report.ReportBuilder;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.reflect.PrettyPrinter;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Primitives;
/**
* Internal class used to handle exceptions.
*
* @author Kristian
*/
public class DetailedErrorReporter implements ErrorReporter {
/**
* Report format for printing the current exception count.
*/
public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!");
public static final String SECOND_LEVEL_PREFIX = " ";
public static final String DEFAULT_PREFIX = " ";
public static final String DEFAULT_SUPPORT_URL = "https://github.com/dmulloy2/ProtocolLib/issues";
// Users that are informed about errors in the chat
public static final String ERROR_PERMISSION = "protocol.info";
// We don't want to spam the server
public static final int DEFAULT_MAX_ERROR_COUNT = 20;
// Prevent spam per plugin too
private ConcurrentMap<String, AtomicInteger> warningCount = new ConcurrentHashMap<String, AtomicInteger>();
protected String prefix;
protected String supportURL;
protected AtomicInteger internalErrorCount = new AtomicInteger();
protected int maxErrorCount;
protected Logger logger;
protected WeakReference<Plugin> pluginReference;
protected String pluginName;
// Whether or not Apache Commons is not present
protected static boolean apacheCommonsMissing;
// Whether or not detailed errror reporting is enabled
protected boolean detailedReporting;
// Map of global objects
protected Map<String, Object> globalParameters = new HashMap<String, Object>();
// Reports to ignore
private ExpireHashMap<Report, Boolean> rateLimited = new ExpireHashMap<Report, Boolean>();
private Object rateLock = new Object();
/**
* Create a default error reporting system.
* @param plugin - the plugin owner.
*/
public DetailedErrorReporter(Plugin plugin) {
this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
}
/**
* Create a central error reporting system.
* @param plugin - the plugin owner.
* @param prefix - default line prefix.
* @param supportURL - URL to report the error.
*/
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) {
this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
}
/**
* Create a central error reporting system.
* @param plugin - the plugin owner.
* @param prefix - default line prefix.
* @param supportURL - URL to report the error.
* @param maxErrorCount - number of errors to print before giving up.
* @param logger - current logger.
*/
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) {
if (plugin == null)
throw new IllegalArgumentException("Plugin cannot be NULL.");
this.pluginReference = new WeakReference<Plugin>(plugin);
this.pluginName = getNameSafely(plugin);
this.prefix = prefix;
this.supportURL = supportURL;
this.maxErrorCount = maxErrorCount;
this.logger = logger;
}
private String getNameSafely(Plugin plugin) {
try {
return plugin.getName();
} catch (LinkageError e) {
return "ProtocolLib";
}
}
// Attempt to get the logger.
private static Logger getBukkitLogger() {
try {
return Bukkit.getLogger();
} catch (LinkageError e) {
return Logger.getLogger("Minecraft");
}
}
/**
* Determine if we're using detailed error reporting.
* @return TRUE if we are, FALSE otherwise.
*/
public boolean isDetailedReporting() {
return detailedReporting;
}
/**
* Set whether or not to use detailed error reporting.
* @param detailedReporting - TRUE to enable it, FALSE otherwise.
*/
public void setDetailedReporting(boolean detailedReporting) {
this.detailedReporting = detailedReporting;
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
if (reportMinimalNoSpam(sender, methodName, error)) {
// Print parameters, if they are given
if (parameters != null && parameters.length > 0) {
logger.log(Level.SEVERE, printParameters(parameters));
}
}
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
reportMinimalNoSpam(sender, methodName, error);
}
/**
* Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports.
* @param sender - the component that observed this exception.
* @param methodName - the method name.
* @param error - the error itself.
* @return TRUE if the error was printed, FALSE if it was suppressed.
*/
public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) {
String pluginName = PacketAdapter.getPluginName(sender);
AtomicInteger counter = warningCount.get(pluginName);
// Thread safe pattern
if (counter == null) {
AtomicInteger created = new AtomicInteger();
counter = warningCount.putIfAbsent(pluginName, created);
if (counter == null) {
counter = created;
}
}
final int errorCount = counter.incrementAndGet();
// See if we should print the full error
if (errorCount < getMaxErrorCount()) {
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " +
methodName + " for " + pluginName, error);
return true;
} else {
// Nope - only print the error count occationally
if (isPowerOfTwo(errorCount)) {
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " +
methodName + " for " + pluginName, error);
}
return false;
}
}
/**
* Determine if a given number is a power of two.
* <p>
* That is, if there exists an N such that 2^N = number.
* @param number - the number to check.
* @return TRUE if the given number is a power of two, FALSE otherwise.
*/
private boolean isPowerOfTwo(int number) {
return (number & (number - 1)) == 0;
}
@Override
public void reportDebug(Object sender, ReportBuilder builder) {
reportDebug(sender, Preconditions.checkNotNull(builder, "builder cannot be NULL").build());
}
@Override
public void reportDebug(Object sender, Report report) {
if (logger.isLoggable(Level.FINE) && canReport(report)) {
reportLevel(Level.FINE, sender, report);
}
}
@Override
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
if (reportBuilder == null)
throw new IllegalArgumentException("reportBuilder cannot be NULL.");
reportWarning(sender, reportBuilder.build());
}
@Override
public void reportWarning(Object sender, Report report) {
if (logger.isLoggable(Level.WARNING) && canReport(report)) {
reportLevel(Level.WARNING, sender, report);
}
}
/**
* Determine if we should print the given report.
* <p>
* The default implementation will check for rate limits.
* @param report - the report to check.
* @return TRUE if we should print it, FALSE otherwise.
*/
protected boolean canReport(Report report) {
long rateLimit = report.getRateLimit();
// Check for rate limit
if (rateLimit > 0) {
synchronized (rateLock) {
if (rateLimited.containsKey(report)) {
return false;
}
rateLimited.put(report, true, rateLimit, TimeUnit.NANOSECONDS);
}
}
return true;
}
private void reportLevel(Level level, Object sender, Report report) {
String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage();
// Print the main warning
if (report.getException() != null) {
logger.log(level, message, report.getException());
} else {
logger.log(level, message);
// Remember the call stack
if (detailedReporting) {
printCallStack(level, logger);
}
}
// Parameters?
if (report.hasCallerParameters()) {
// Write it
logger.log(level, printParameters(report.getCallerParameters()));
}
}
/**
* Retrieve the name of a sender class.
* @param sender - sender object.
* @return The name of the sender's class.
*/
private String getSenderName(Object sender) {
if (sender != null)
return ReportType.getSenderClass(sender).getSimpleName();
else
return "NULL";
}
@Override
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
reportDetailed(sender, reportBuilder.build());
}
@Override
public void reportDetailed(Object sender, Report report) {
final Plugin plugin = pluginReference.get();
final int errorCount = internalErrorCount.incrementAndGet();
// Do not overtly spam the server!
if (errorCount > getMaxErrorCount()) {
// Only allow the error count at rare occations
if (isPowerOfTwo(errorCount)) {
// Permit it - but print the number of exceptions first
reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build());
} else {
// NEVER SPAM THE CONSOLE
return;
}
}
// Secondary rate limit
if (!canReport(report)) {
return;
}
StringWriter text = new StringWriter();
PrintWriter writer = new PrintWriter(text);
// Helpful message
writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage());
writer.println("If this problem hasn't already been reported, please open a ticket");
writer.println("at " + supportURL + " with the following data:");
// Now, let us print important exception information
writer.println("Stack Trace:");
if (report.getException() != null) {
report.getException().printStackTrace(writer);
} else if (detailedReporting) {
printCallStack(writer);
}
// Data dump!
writer.println("Dump:");
// Relevant parameters
if (report.hasCallerParameters()) {
printParameters(writer, report.getCallerParameters());
}
// Global parameters
for (String param : globalParameters()) {
writer.println(SECOND_LEVEL_PREFIX + param + ":");
writer.println(addPrefix(getStringDescription(getGlobalParameter(param)),
SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX));
}
// Now, for the sender itself
writer.println("Sender:");
writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX));
// And plugin
if (plugin != null) {
writer.println("Version:");
writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX));
}
// And java version
writer.println("Java Version:");
writer.println(addPrefix(System.getProperty("java.version"), SECOND_LEVEL_PREFIX));
// Add the server version too
if (Bukkit.getServer() != null) {
writer.println("Server:");
writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
// Inform of this occurrence
if (ERROR_PERMISSION != null) {
Bukkit.getServer().broadcast(
String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender),
ERROR_PERMISSION
);
}
}
// Make sure it is reported
logger.severe(addPrefix(text.toString(), prefix));
}
/**
* Print the call stack to the given logger.
* @param logger - the logger.
*/
private void printCallStack(Level level, Logger logger) {
StringWriter text = new StringWriter();
printCallStack(new PrintWriter(text));
// Print the exception
logger.log(level, text.toString());
}
/**
* Print the current call stack.
* @param writer - the writer.
*/
private void printCallStack(PrintWriter writer) {
Exception current = new Exception("Not an error! This is the call stack.");
current.printStackTrace(writer);
}
private String printParameters(Object... parameters) {
StringWriter writer = new StringWriter();
// Print and retrieve the string buffer
printParameters(new PrintWriter(writer), parameters);
return writer.toString();
}
private void printParameters(PrintWriter writer, Object[] parameters) {
writer.println("Parameters: ");
// We *really* want to get as much information as possible
for (Object param : parameters) {
writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX));
}
}
/**
* Adds the given prefix to every line in the text.
* @param text - text to modify.
* @param prefix - prefix added to every line in the text.
* @return The modified text.
*/
protected String addPrefix(String text, String prefix) {
return text.replaceAll("(?m)^", prefix);
}
/**
* Retrieve a string representation of the given object.
* @param value - object to convert.
* @return String representation.
*/
public static String getStringDescription(Object value) {
// We can't only rely on toString.
if (value == null) {
return "[NULL]";
} if (isSimpleType(value) || value instanceof Class<?>) {
return value.toString();
} else {
try {
if (!apacheCommonsMissing)
return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null));
} catch (LinkageError ex) {
// Apache is probably missing
apacheCommonsMissing = true;
} catch (Exception e) {
// Don't use the error logger to log errors in error logging (that could lead to infinite loops)
ProtocolLibrary.log(Level.WARNING, "Cannot convert to a String with Apache: " + e.getMessage());
}
// Use our custom object printer instead
try {
return PrettyPrinter.printObject(value, value.getClass(), Object.class);
} catch (IllegalAccessException e) {
return "[Error: " + e.getMessage() + "]";
}
}
}
/**
* Determine if the given object is a wrapper for a primitive/simple type or not.
* @param test - the object to test.
* @return TRUE if this object is simple enough to simply be printed, FALSE othewise.
*/
protected static boolean isSimpleType(Object test) {
return test instanceof String || Primitives.isWrapperType(test.getClass());
}
/**
* Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}.
* @return Number of errors printed.
*/
public int getErrorCount() {
return internalErrorCount.get();
}
/**
* Set the number of errors printed.
* @param errorCount - new number of errors printed.
*/
public void setErrorCount(int errorCount) {
internalErrorCount.set(errorCount);
}
/**
* Retrieve the maximum number of errors we can print before we begin suppressing errors.
* @return Maximum number of errors.
*/
public int getMaxErrorCount() {
return maxErrorCount;
}
/**
* Set the maximum number of errors we can print before we begin suppressing errors.
* @param maxErrorCount - new max count.
*/
public void setMaxErrorCount(int maxErrorCount) {
this.maxErrorCount = maxErrorCount;
}
/**
* Adds the given global parameter. It will be included in every error report.
* <p>
* Both key and value must be non-null.
* @param key - name of parameter.
* @param value - the global parameter itself.
*/
public void addGlobalParameter(String key, Object value) {
if (key == null)
throw new IllegalArgumentException("key cannot be NULL.");
if (value == null)
throw new IllegalArgumentException("value cannot be NULL.");
globalParameters.put(key, value);
}
/**
* Retrieve a global parameter by its key.
* @param key - key of the parameter to retrieve.
* @return The value of the global parameter, or NULL if not found.
*/
public Object getGlobalParameter(String key) {
if (key == null)
throw new IllegalArgumentException("key cannot be NULL.");
return globalParameters.get(key);
}
/**
* Reset all global parameters.
*/
public void clearGlobalParameters() {
globalParameters.clear();
}
/**
* Retrieve a set of every registered global parameter.
* @return Set of all registered global parameters.
*/
public Set<String> globalParameters() {
return globalParameters.keySet();
}
/**
* Retrieve the support URL that will be added to all detailed reports.
* @return Support URL.
*/
public String getSupportURL() {
return supportURL;
}
/**
* Set the support URL that will be added to all detailed reports.
* @param supportURL - the new support URL.
*/
public void setSupportURL(String supportURL) {
this.supportURL = supportURL;
}
/**
* Retrieve the prefix to apply to every line in the error reports.
* @return Error report prefix.
*/
public String getPrefix() {
return prefix;
}
/**
* Set the prefix to apply to every line in the error reports.
* @param prefix - new prefix.
*/
public void setPrefix(String prefix) {
this.prefix = prefix;
}
/**
* Retrieve the current logger that is used to print all reports.
* @return The current logger.
*/
public Logger getLogger() {
return logger;
}
/**
* Set the current logger that is used to print all reports.
* @param logger - new logger.
*/
public void setLogger(Logger logger) {
this.logger = logger;
}
}
@@ -0,0 +1,90 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
/**
* Represents an object that can forward an error {@link Report} to the display and permanent storage.
*
* @author Kristian
*/
public interface ErrorReporter {
/**
* Prints a small minimal error report regarding an exception from another plugin.
* @param sender - the other plugin.
* @param methodName - name of the caller method.
* @param error - the exception itself.
*/
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error);
/**
* Prints a small minimal error report regarding an exception from another plugin.
* @param sender - the other plugin.
* @param methodName - name of the caller method.
* @param error - the exception itself.
* @param parameters - any relevant parameters to print.
*/
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters);
/**
* Prints a debug message from the current sender.
* <p>
* Most users will not see this message.
* @param sender - the sender.
* @param report - the report.
*/
public abstract void reportDebug(Object sender, Report report);
/**
* Prints a debug message from the current sender.
* @param sender - the sender.
* @param builder - the report builder.
*/
public abstract void reportDebug(Object sender, ReportBuilder builder);
/**
* Prints a warning message from the current plugin.
* @param sender - the object containing the caller method.
* @param report - an error report to include.
*/
public abstract void reportWarning(Object sender, Report report);
/**
* Prints a warning message from the current plugin.
* @param sender - the object containing the caller method.
* @param reportBuilder - an error report builder that will be used to get the report.
*/
public abstract void reportWarning(Object sender, ReportBuilder reportBuilder);
/**
* Prints a detailed error report about an unhandled exception.
* @param sender - the object containing the caller method.
* @param report - an error report to include.
*/
public abstract void reportDetailed(Object sender, Report report);
/**
* Prints a detailed error report about an unhandled exception.
* @param sender - the object containing the caller method.
* @param reportBuilder - an error report builder that will be used to get the report.
*/
public abstract void reportDetailed(Object sender, ReportBuilder reportBuilder);
}
@@ -0,0 +1,108 @@
package com.comphenix.protocol.error;
import java.io.File;
import java.net.URLDecoder;
import java.security.CodeSource;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import com.google.common.base.Preconditions;
public final class PluginContext {
// Determine plugin folder
private static File pluginFolder;
private PluginContext() {
// Not constructable
}
/**
* Retrieve the name of the plugin that called the last method(s) in the exception.
* @param ex - the exception.
* @return The name of the plugin, or NULL.
*/
public static String getPluginCaller(Exception ex) {
StackTraceElement[] elements = ex.getStackTrace();
String current = getPluginName(elements[0]);
for (int i = 1; i < elements.length; i++) {
String caller = getPluginName(elements[i]);
if (caller != null && !caller.equals(current)) {
return caller;
}
}
return null;
}
/**
* Lookup the plugin that this method invocation belongs to, and return its file name.
* @param element - the method invocation.
* @return Plugin name, or NULL if not found.
*/
public static String getPluginName(StackTraceElement element) {
try {
if (Bukkit.getServer() == null) {
return null;
}
CodeSource codeSource = Class.forName(element.getClassName()).getProtectionDomain().getCodeSource();
if (codeSource != null) {
String encoding = codeSource.getLocation().getPath();
File path = new File(URLDecoder.decode(encoding, "UTF-8"));
File plugins = getPluginFolder();
if (plugins != null && folderContains(plugins, path)) {
return path.getName().replaceAll(".jar", "");
}
}
} catch (Throwable ex) {
// throw new RuntimeException("Cannot lookup plugin name.", ex);
}
return null; // Cannot find it
}
/**
* Determine if a folder contains the given file.
* @param folder - the folder.
* @param file - the file.
* @return TRUE if it does, FALSE otherwise.
*/
private static boolean folderContains(File folder, File file) {
Preconditions.checkNotNull(folder, "folder cannot be NULL");
Preconditions.checkNotNull(file, "file cannot be NULL");
// Get absolute versions
folder = folder.getAbsoluteFile();
file = file.getAbsoluteFile();
while (file != null) {
if (folder.equals(file))
return true;
file = file.getParentFile();
}
return false;
}
/**
* Retrieve the folder that contains every plugin on the server.
* @return Folder with every plugin, or NULL if Bukkit has not been initialized yet.
*/
private static File getPluginFolder() {
File folder = pluginFolder;
if (folder == null && Bukkit.getServer() != null) {
Plugin[] plugins = Bukkit.getPluginManager().getPlugins();
if (plugins.length > 0) {
folder = plugins[0].getDataFolder().getParentFile();
pluginFolder = folder;
}
}
return folder;
}
}
@@ -0,0 +1,240 @@
package com.comphenix.protocol.error;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/**
* Represents a error or warning report.
*
* @author Kristian
*/
public class Report {
private final ReportType type;
private final Throwable exception;
private final Object[] messageParameters;
private final Object[] callerParameters;
// Used to rate limit reports that are similar
private final long rateLimit;
/**
* Must be constructed through the factory method in Report.
*/
public static class ReportBuilder {
private ReportType type;
private Throwable exception;
private Object[] messageParameters;
private Object[] callerParameters;
private long rateLimit;
private ReportBuilder() {
// Don't allow
}
/**
* Set the current report type. Cannot be NULL.
* @param type - report type.
* @return This builder, for chaining.
*/
public ReportBuilder type(ReportType type) {
if (type == null)
throw new IllegalArgumentException("Report type cannot be set to NULL.");
this.type = type;
return this;
}
/**
* Set the current exception that occurred.
* @param exception - exception that occurred.
* @return This builder, for chaining.
*/
public ReportBuilder error(@Nullable Throwable exception) {
this.exception = exception;
return this;
}
/**
* Set the message parameters that are used to construct a message text.
* @param messageParameters - parameters for the report type.
* @return This builder, for chaining.
*/
public ReportBuilder messageParam(@Nullable Object... messageParameters) {
this.messageParameters = messageParameters;
return this;
}
/**
* Set the parameters in the caller method. This is optional.
* @param callerParameters - parameters of the caller method.
* @return This builder, for chaining.
*/
public ReportBuilder callerParam(@Nullable Object... callerParameters) {
this.callerParameters = callerParameters;
return this;
}
/**
* Set the minimum number of nanoseconds to wait until a report of equal type and parameters
* is allowed to be printed again.
* @param rateLimit - number of nanoseconds, or 0 to disable. Cannot be negative.
* @return This builder, for chaining.
*/
public ReportBuilder rateLimit(long rateLimit) {
if (rateLimit < 0)
throw new IllegalArgumentException("Rate limit cannot be less than zero.");
this.rateLimit = rateLimit;
return this;
}
/**
* Set the minimum time to wait until a report of equal type and parameters is allowed to be printed again.
* @param rateLimit - the time, or 0 to disable. Cannot be negative.
* @param rateUnit - the unit of the rate limit.
* @return This builder, for chaining.
*/
public ReportBuilder rateLimit(long rateLimit, TimeUnit rateUnit) {
return rateLimit(TimeUnit.NANOSECONDS.convert(rateLimit, rateUnit));
}
/**
* Construct a new report with the provided input.
* @return A new report.
*/
public Report build() {
return new Report(type, exception, messageParameters, callerParameters, rateLimit);
}
}
/**
* Construct a new report builder.
* @param type - the initial report type.
* @return Report builder.
*/
public static ReportBuilder newBuilder(ReportType type) {
return new ReportBuilder().type(type);
}
/**
* Construct a new report with the given type and parameters.
* @param exception - exception that occured in the caller method.
* @param type - the report type that will be used to construct the message.
* @param messageParameters - parameters used to construct the report message.
* @param callerParameters - parameters from the caller method.
*/
protected Report(ReportType type, @Nullable Throwable exception,
@Nullable Object[] messageParameters, @Nullable Object[] callerParameters) {
this(type, exception, messageParameters, callerParameters, 0);
}
/**
* Construct a new report with the given type and parameters.
* @param exception - exception that occurred in the caller method.
* @param type - the report type that will be used to construct the message.
* @param messageParameters - parameters used to construct the report message.
* @param callerParameters - parameters from the caller method.
* @param rateLimit - minimum number of nanoseconds to wait until a report of equal type and parameters is allowed to be printed again.
*/
protected Report(ReportType type, @Nullable Throwable exception,
@Nullable Object[] messageParameters, @Nullable Object[] callerParameters, long rateLimit) {
if (type == null)
throw new IllegalArgumentException("type cannot be NULL.");
this.type = type;
this.exception = exception;
this.messageParameters = messageParameters;
this.callerParameters = callerParameters;
this.rateLimit = rateLimit;
}
/**
* Format the current report type with the provided message parameters.
* @return The formated report message.
*/
public String getReportMessage() {
return type.getMessage(messageParameters);
}
/**
* Retrieve the message parameters that will be used to construc the report message.
* <p>
* This should not be confused with the method parameters of the caller method.
* @return Message parameters.
*/
public Object[] getMessageParameters() {
return messageParameters;
}
/**
* Retrieve the parameters of the caller method. Optional - may be NULL.
* @return Parameters or the caller method.
*/
public Object[] getCallerParameters() {
return callerParameters;
}
/**
* Retrieve the report type.
* @return Report type.
*/
public ReportType getType() {
return type;
}
/**
* Retrieve the associated exception, or NULL if not found.
* @return Associated exception, or NULL.
*/
public Throwable getException() {
return exception;
}
/**
* Determine if we have any message parameters.
* @return TRUE if there are any message parameters, FALSE otherwise.
*/
public boolean hasMessageParameters() {
return messageParameters != null && messageParameters.length > 0;
}
/**
* Determine if we have any caller parameters.
* @return TRUE if there are any caller parameters, FALSE otherwise.
*/
public boolean hasCallerParameters() {
return callerParameters != null && callerParameters.length > 0;
}
/**
* Retrieve desired minimum number of nanoseconds until a report of the same type and parameters should be reprinted.
* <p>
* Note that this may be ignored or modified by the error reporter. Zero indicates no rate limit.
* @return The number of nanoseconds. Never negative.
*/
public long getRateLimit() {
return rateLimit;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(callerParameters);
result = prime * result + Arrays.hashCode(messageParameters);
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj instanceof Report) {
Report other = (Report) obj;
return type == other.type &&
Arrays.equals(callerParameters, other.callerParameters) &&
Arrays.equals(messageParameters, other.messageParameters);
}
return false;
}
}
@@ -0,0 +1,148 @@
package com.comphenix.protocol.error;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
/**
* Represents a strongly-typed report. Subclasses should be immutable.
* <p>
* By convention, a report must be declared as a static field publicly accessible from the sender class.
* @author Kristian
*/
public class ReportType {
private final String errorFormat;
// Used to store the report name
protected String reportName;
/**
* Construct a new report type.
* @param errorFormat - string used to format the underlying report.
*/
public ReportType(String errorFormat) {
this.errorFormat = errorFormat;
}
/**
* Convert the given report to a string, using the provided parameters.
* @param parameters - parameters to insert, or NULL to insert nothing.
* @return The full report in string format.
*/
public String getMessage(Object[] parameters) {
if (parameters == null || parameters.length == 0)
return toString();
else
return String.format(errorFormat, parameters);
}
@Override
public String toString() {
return errorFormat;
}
/**
* Retrieve the class of the given sender.
* <p>
* If the sender is already a Class, we return it.
* @param sender - the sender to look up.
* @return The class of the sender.
*/
public static Class<?> getSenderClass(Object sender) {
if (sender == null)
throw new IllegalArgumentException("sender cannot be NUll.");
else if (sender instanceof Class<?>)
return (Class<?>) sender;
else
return sender.getClass();
}
/**
* Retrieve the full canonical name of a given report type.
* <p>
* Note that the sender may be a class (for static callers), in which
* case it will be used directly instead of its getClass() method.
* <p>
* It is thus not advisable for class classes to report reports.
* @param sender - the sender, or its class.
* @param type - the report type.
* @return The full canonical name.
*/
public static String getReportName(Object sender, ReportType type) {
if (sender == null)
throw new IllegalArgumentException("sender cannot be NUll.");
return getReportName(getSenderClass(sender), type);
}
/**
* Retrieve the full canonical name of a given report type.
* <p>
* This is in the format <i>canonical_name_of_class#report_type</i>
* @param clazz - the sender class.
* @param type - the report instance.
* @return The full canonical name.
*/
private static String getReportName(Class<?> sender, ReportType type) {
if (sender == null)
throw new IllegalArgumentException("sender cannot be NUll.");
// Whether or not we need to retrieve the report name again
if (type.reportName == null) {
for (Field field : getReportFields(sender)) {
try {
field.setAccessible(true);
if (field.get(null) == type) {
// We got the right field!
return type.reportName = field.getDeclaringClass().getCanonicalName() + "#" + field.getName();
}
} catch (IllegalAccessException e) {
throw new FieldAccessException("Unable to read field " + field, e);
}
}
throw new IllegalArgumentException("Cannot find report name for " + type);
}
return type.reportName;
}
/**
* Retrieve all publicly associated reports.
* @param sender - sender class.
* @return All associated reports.
*/
public static ReportType[] getReports(Class<?> sender) {
if (sender == null)
throw new IllegalArgumentException("sender cannot be NULL.");
List<ReportType> result = new ArrayList<ReportType>();
// Go through all the fields
for (Field field : getReportFields(sender)) {
try {
field.setAccessible(true);
result.add((ReportType) field.get(null));
} catch (IllegalAccessException e) {
throw new FieldAccessException("Unable to read field " + field, e);
}
}
return result.toArray(new ReportType[0]);
}
/**
* Retrieve all publicly associated report fields.
* @param clazz - sender class.
* @return All associated report fields.
*/
private static List<Field> getReportFields(Class<?> clazz) {
return FuzzyReflection.fromClass(clazz).getFieldList(
FuzzyFieldContract.newBuilder().
requireModifier(Modifier.STATIC).
typeDerivedOf(ReportType.class).
build()
);
}
}
@@ -0,0 +1,53 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import com.google.common.base.Joiner;
/**
* Represents an error reporter that rethrows every exception instead.
* @author Kristian
*/
public class RethrowErrorReporter implements ErrorReporter {
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
throw new RuntimeException("Minimal error by " + sender + " in " + methodName, error);
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
throw new RuntimeException(
"Minimal error by " + sender + " in " + methodName + " with " + Joiner.on(",").join(parameters), error);
}
@Override
public void reportDebug(Object sender, Report report) {
// Do nothing - this is just a debug
}
@Override
public void reportDebug(Object sender, ReportBuilder builder) {
// As above
}
@Override
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
reportWarning(sender, reportBuilder.build());
}
@Override
public void reportWarning(Object sender, Report report) {
throw new RuntimeException("Warning by " + sender + ": " + report);
}
@Override
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
reportDetailed(sender, reportBuilder.build());
}
@Override
public void reportDetailed(Object sender, Report report) {
throw new RuntimeException("Detailed error " + sender + ": " + report, report.getException());
}
}
@@ -17,6 +17,8 @@
package com.comphenix.protocol.events;
import com.comphenix.protocol.PacketType.Sender;
/**
* Used to set a packet filter.
*
@@ -45,4 +47,44 @@ public enum ConnectionSide {
public boolean isForServer() {
return this == SERVER_SIDE || this == BOTH;
}
/**
* Retrieve the sender of this connection side.
* <p>
* This is NULL for {@link #BOTH}.
* @return The sender.
*/
public Sender getSender() {
if (this == SERVER_SIDE)
return Sender.SERVER;
else if (this == CLIENT_SIDE)
return Sender.CLIENT;
return null;
}
/**
* If both connection sides are present, return {@link #BOTH} - otherwise, return the one valud connection side.
* <p>
* NULL is not a valid connection side.
* @param a - the first connection side.
* @param b - the second connection side.
* @return BOTH or the one valid side, or NULL.
*/
public static ConnectionSide add(ConnectionSide a, ConnectionSide b) {
if (a == null)
return b;
if (b == null)
return a;
// Now merge them together
boolean client = a.isForClient() || b.isForClient();
boolean server = a.isForServer() || b.isForServer();
if (client && server)
return BOTH;
else if (client)
return CLIENT_SIDE;
else
return SERVER_SIDE;
}
}
@@ -0,0 +1,31 @@
package com.comphenix.protocol.events;
import com.comphenix.protocol.injector.GamePhase;
/**
* Represents additional options a listener may require.
*
* @author Kristian
*/
public enum ListenerOptions {
/**
* Retrieve the serialized client packet as it appears on the network stream.
*/
INTERCEPT_INPUT_BUFFER,
/**
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when
* a packet ID is known to be transmitted during login.
*/
DISABLE_GAMEPHASE_DETECTION,
/**
* Do not verify that the owning plugin has a vaid plugin.yml.
*/
SKIP_PLUGIN_VERIFIER,
/**
* Notify ProtocolLib that {@link PacketListener#onPacketSending(PacketEvent)} is thread safe.
*/
ASYNC;
}
@@ -2,16 +2,16 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
@@ -47,7 +47,7 @@ public enum ListenerPriority {
HIGHEST(4),
/**
* Event is listened to purely for monitoring the outcome of an event.
* <p/>
* <p>
* No modifications to the event should be made under this priority.
*/
MONITOR(5);
@@ -2,95 +2,143 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.events;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.google.common.base.Objects;
import com.google.common.collect.Sets;
/**
* Determines which packets will be observed by a listener, and with what priority.
* @author Kristian
* @author Kristian
*/
public class ListeningWhitelist {
/**
* A whitelist with no packets - indicates that the listener shouldn't observe any packets.
*/
public static final ListeningWhitelist EMPTY_WHITELIST = new ListeningWhitelist(ListenerPriority.LOW);
private ListenerPriority priority;
private Set<Integer> whitelist;
private GamePhase gamePhase;
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* @param priority - the listener priority.
* @param whitelist - set of IDs to observe/enable.
*/
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist) {
this(priority, whitelist, GamePhase.PLAYING);
private final ListenerPriority priority;
private final GamePhase gamePhase;
private final Set<ListenerOptions> options;
private final Set<PacketType> types;
// Cache whitelist
private transient Set<Integer> intWhitelist;
private ListeningWhitelist(Builder builder) {
this.priority = builder.priority;
this.types = builder.types;
this.gamePhase = builder.gamePhase;
this.options = builder.options;
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - set of IDs to observe/enable.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist) {
this(priority, whitelist, GamePhase.PLAYING);
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - set of IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist, GamePhase gamePhase) {
this.priority = priority;
this.whitelist = whitelist;
this.types = PacketRegistry.toPacketTypes(safeSet(whitelist));
this.gamePhase = gamePhase;
this.options = EnumSet.noneOf(ListenerOptions.class);
}
/**
* Creates a packet whitelist of a given priority for a list of packets.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) {
this.priority = priority;
this.whitelist = Sets.newHashSet(whitelist);
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
this.gamePhase = GamePhase.PLAYING;
this.options = EnumSet.noneOf(ListenerOptions.class);
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase) {
this.priority = priority;
this.whitelist = Sets.newHashSet(whitelist);
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
this.gamePhase = gamePhase;
this.options = EnumSet.noneOf(ListenerOptions.class);
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs and options.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
* @param options - every special option associated with this whitelist.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase, ListenerOptions... options) {
this.priority = priority;
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
this.gamePhase = gamePhase;
this.options = safeEnumSet(Arrays.asList(options), ListenerOptions.class);
}
/**
* Whether or not this whitelist has any enabled packets.
* @return TRUE if there are any packets, FALSE otherwise.
*/
public boolean isEnabled() {
return whitelist != null && whitelist.size() > 0;
return types != null && types.size() > 0;
}
/**
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
* @return Execution order in terms of priority.
@@ -98,15 +146,28 @@ public class ListeningWhitelist {
public ListenerPriority getPriority() {
return priority;
}
/**
* Retrieves the list of packets that will be observed by the listeners.
* <p>
* Deprecated: Use {@link #getTypes()} instead.
* @return Packet whitelist.
*/
@Deprecated
public Set<Integer> getWhitelist() {
return whitelist;
if (intWhitelist == null)
intWhitelist = PacketRegistry.toLegacy(types);
return intWhitelist;
}
/**
* Retrieves a set of the packets that will be observed by the listeners.
* @return Packet whitelist.
*/
public Set<PacketType> getTypes() {
return types;
}
/**
* Retrieve which game phase this listener is active under.
* @return The active game phase.
@@ -115,15 +176,23 @@ public class ListeningWhitelist {
return gamePhase;
}
/**
* Retrieve every special option associated with this whitelist.
* @return Every special option.
*/
public Set<ListenerOptions> getOptions() {
return Collections.unmodifiableSet(options);
}
@Override
public int hashCode(){
return Objects.hashCode(priority, whitelist, gamePhase);
public int hashCode() {
return Objects.hashCode(priority, types, gamePhase, options);
}
/**
* Determine if any of the given IDs can be found in the whitelist.
* @param whitelist - whitelist to test.
* @param idList - list of packet IDs to find.
* @param idList - list of packet IDs to find.
* @return TRUE if any of the packets in the list can be found in the whitelist, FALSE otherwise.
*/
public static boolean containsAny(ListeningWhitelist whitelist, int... idList) {
@@ -133,29 +202,283 @@ public class ListeningWhitelist {
return true;
}
}
return false;
}
@Override
public boolean equals(final Object obj){
if(obj instanceof ListeningWhitelist){
final ListeningWhitelist other = (ListeningWhitelist) obj;
return Objects.equal(priority, other.priority)
&& Objects.equal(whitelist, other.whitelist);
} else{
return false;
}
/**
* Determine if the given whitelist is empty or not.
* @param whitelist - the whitelist to test.
* @return TRUE if the whitelist is empty, FALSE otherwise.
*/
public static boolean isEmpty(ListeningWhitelist whitelist) {
if (whitelist == EMPTY_WHITELIST)
return true;
else if (whitelist == null)
return true;
else
return whitelist.getTypes().isEmpty();
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof ListeningWhitelist) {
final ListeningWhitelist other = (ListeningWhitelist) obj;
return Objects.equal(priority, other.priority)
&& Objects.equal(types, other.types)
&& Objects.equal(gamePhase, other.gamePhase)
&& Objects.equal(options, other.options);
} else {
return false;
}
}
@Override
public String toString() {
if (this == EMPTY_WHITELIST)
return "EMPTY_WHITELIST";
else
return Objects.toStringHelper(this)
.add("priority", priority)
.add("packets", whitelist).toString();
return Objects.toStringHelper(this).
add("priority", priority).
add("packets", types).
add("gamephase", gamePhase).
add("options", options).
toString();
}
/**
* Construct a new builder of whitelists.
* @return New whitelist builder.
*/
public static Builder newBuilder() {
return new Builder(null);
}
/**
* Construct a new builder of whitelists initialized to the same values as the template.
* @param template - the template object.
* @return New whitelist builder.
*/
public static Builder newBuilder(ListeningWhitelist template) {
return new Builder(template);
}
/**
* Construct a copy of a given enum.
* @param options - the options to copy, or NULL to indicate the empty set.
* @return A copy of the enum set.
*/
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
if (options != null && !options.isEmpty()) {
return EnumSet.copyOf(options);
} else {
return EnumSet.noneOf(enumClass);
}
}
/**
* Construct a copy of a given set.
* @param list - the set to copy.
* @return The copied set.
*/
private static <T> Set<T> safeSet(Collection<T> set) {
if (set != null)
return Sets.newHashSet(set);
else
return Collections.emptySet();
}
/**
* Represents a builder of whitelists.
* @author Kristian
*/
public static class Builder {
// Default values
private ListenerPriority priority = ListenerPriority.NORMAL;
private Set<PacketType> types = Sets.newHashSet();
private GamePhase gamePhase = GamePhase.PLAYING;
private Set<ListenerOptions> options = Sets.newHashSet();
/**
* Construct a new listening whitelist template.
* @param template - the template.
*/
private Builder(ListeningWhitelist template) {
if (template != null) {
priority(template.getPriority());
gamePhase(template.getGamePhase());
types(template.getTypes());
options(template.getOptions());
}
}
/**
* Set the priority to use when constructing new whitelists.
* @param priority - the priority.
* @return This builder, for chaining.
*/
public Builder priority(ListenerPriority priority) {
this.priority = priority;
return this;
}
/**
* Set the priority of the whitelist to monitor.
* @return This builder, for chaining.
*/
public Builder monitor() {
return priority(ListenerPriority.MONITOR);
}
/**
* Set the priority of the whitelist to normal.
* @return This builder, for chaining.
*/
public Builder normal() {
return priority(ListenerPriority.NORMAL);
}
/**
* Set the priority of the whitelist to lowest.
* @return This builder, for chaining.
*/
public Builder lowest() {
return priority(ListenerPriority.LOWEST);
}
/**
* Set the priority of the whitelist to low.
* @return This builder, for chaining.
*/
public Builder low() {
return priority(ListenerPriority.LOW);
}
/**
* Set the priority of the whitelist to highest.
* @return This builder, for chaining.
*/
public Builder highest() {
return priority(ListenerPriority.HIGHEST);
}
/**
* Set the priority of the whitelist to high.
* @return This builder, for chaining.
*/
public Builder high() {
return priority(ListenerPriority.HIGH);
}
/**
* Set the whitelist of packet IDs to copy when constructing new whitelists.
* <p>
* Deprecated: Use {@link #types(Collection)} instead.
* @param whitelist - the whitelist of packets.
* @return This builder, for chaining.
*/
@Deprecated
public Builder whitelist(Collection<Integer> whitelist) {
this.types = PacketRegistry.toPacketTypes(safeSet(whitelist));
return this;
}
/**
* Set the whitelist of packet types to copy when constructing new whitelists.
* @param types - the whitelist of packets.
* @return This builder, for chaining.
*/
public Builder types(PacketType... types) {
this.types = safeSet(Sets.newHashSet(types));
return this;
}
/**
* Set the whitelist of packet types to copy when constructing new whitelists.
* @param types - the whitelist of packets.
* @return This builder, for chaining.
*/
public Builder types(Collection<PacketType> types) {
this.types = safeSet(types);
return this;
}
/**
* Set the gamephase to use when constructing new whitelists.
* @param gamePhase - the gamephase.
* @return This builder, for chaining.
*/
public Builder gamePhase(GamePhase gamePhase) {
this.gamePhase = gamePhase;
return this;
}
/**
* Set the gamephase to {@link GamePhase#BOTH}.
* @return This builder, for chaining.
*/
public Builder gamePhaseBoth() {
return gamePhase(GamePhase.BOTH);
}
/**
* Set the options to copy when constructing new whitelists.
* @param options - the options.
* @return This builder, for chaining.
*/
public Builder options(Set<ListenerOptions> options) {
this.options = safeSet(options);
return this;
}
/**
* Set the options to copy when constructing new whitelists.
* @param options - the options.
* @return This builder, for chaining.
*/
public Builder options(Collection<ListenerOptions> options) {
this.options = safeSet(options);
return this;
}
/**
* Set the options to copy when constructing new whitelists.
* @param serverOptions - the options array.
* @return This builder, for chaining.
*/
public Builder options(ListenerOptions[] serverOptions) {
this.options = safeSet(Sets.newHashSet(serverOptions));
return this;
}
/**
* Options to merge into the current set of options.
* @param serverOptions - the options array.
* @return This builder, for chaining.
*/
public Builder mergeOptions(ListenerOptions... serverOptions) {
return mergeOptions(Arrays.asList(serverOptions));
}
/**
* Options to merge into the current set of options.
* @param serverOptions - the options array.
* @return This builder, for chaining.
*/
public Builder mergeOptions(Collection<ListenerOptions> serverOptions) {
if (options == null)
return options(serverOptions);
// Merge the options
this.options.addAll(serverOptions);
return this;
}
/**
* Construct a new whitelist from the values in this builder.
* @return The new whitelist.
*/
public ListeningWhitelist build() {
return new ListeningWhitelist(this);
}
}
}
@@ -1,3 +1,20 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.events;
import java.util.logging.Level;
@@ -6,15 +23,12 @@ import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
/**
* Represents a listener that is notified of every sent and recieved packet.
* Represents a listener that is notified of every sent and received packet.
*
* @author Kristian
*/
@@ -32,15 +46,16 @@ public abstract class MonitorAdapter implements PacketListener {
initialize(plugin, side, logger);
}
@SuppressWarnings("deprecation")
private void initialize(Plugin plugin, ConnectionSide side, Logger logger) {
this.plugin = plugin;
// Recover in case something goes wrong
try {
if (side.isForServer())
this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getSupported(), GamePhase.BOTH);
this.sending = ListeningWhitelist.newBuilder().monitor().types(PacketRegistry.getServerPacketTypes()).gamePhaseBoth().build();
if (side.isForClient())
this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getSupported(), GamePhase.BOTH);
this.receiving = ListeningWhitelist.newBuilder().monitor().types(PacketRegistry.getClientPacketTypes()).gamePhaseBoth().build();
} catch (FieldAccessException e) {
if (side.isForServer())
this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getRegistry().values(), GamePhase.BOTH);
@@ -0,0 +1,421 @@
package com.comphenix.protocol.events;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import javax.annotation.Nonnull;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.utility.ByteBufferInputStream;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
/**
* Marker containing the serialized packet data seen from the network,
* or output handlers that will serialize the current packet.
*
* @author Kristian
*/
public abstract class NetworkMarker {
public static class EmptyBufferMarker extends NetworkMarker {
public EmptyBufferMarker(@Nonnull ConnectionSide side) {
super(side, (byte[]) null, null);
}
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
throw new IllegalStateException("Buffer is empty.");
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
@Override
protected DataInputStream addHeader(DataInputStream input, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
}
// Custom network handler
private PriorityQueue<PacketOutputHandler> outputHandlers;
// Post listeners
private List<PacketPostListener> postListeners;
// Post packets
private List<ScheduledPacket> scheduledPackets;
// The input buffer
private ByteBuffer inputBuffer;
private final ConnectionSide side;
private final PacketType type;
// Cache serializer too
private StreamSerializer serializer;
/**
* Construct a new network marker.
* @param side - which side this marker belongs to.
* @param inputBuffer - the read serialized packet data.
* @param type - packet type
*/
public NetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer, PacketType type) {
this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
this.inputBuffer = inputBuffer;
this.type = type;
}
/**
* Construct a new network marker.
* <p>
* The input buffer is only non-null for client-side packets.
* @param side - which side this marker belongs to.
* @param inputBuffer - the read serialized packet data.
* @param type - packet type
*/
public NetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer, PacketType type) {
this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
this.type = type;
if (inputBuffer != null) {
this.inputBuffer = ByteBuffer.wrap(inputBuffer);
}
}
/**
* Retrieve whether or not this marker belongs to a client or a server side packet.
* @return The side the parent packet belongs to.
*/
public ConnectionSide getSide() {
return side;
}
/**
* Retrieve a utility class for serializing and deserializing Minecraft objects.
* @return Serialization utility class.
*/
public StreamSerializer getSerializer() {
if (serializer == null)
serializer = new StreamSerializer();
return serializer;
}
/**
* Retrieve the serialized packet data (excluding the header by default) from the network input stream.
* <p>
* The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}.
* <p>
* It returns NULL if the packet was transmitted by a plugin locally.
* @return A byte buffer containing the raw packet data read from the network.
*/
public ByteBuffer getInputBuffer() {
return getInputBuffer(true);
}
/**
* Retrieve the serialized packet data from the network input stream.
* <p>
* The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}.
* <p>
* It returns NULL if the packet was transmitted by a plugin locally.
* @param excludeHeader - whether or not to exclude the packet ID header.
* @return A byte buffer containing the raw packet data read from the network.
*/
public ByteBuffer getInputBuffer(boolean excludeHeader) {
if (side.isForServer())
throw new IllegalStateException("Server-side packets have no input buffer.");
if (inputBuffer != null) {
ByteBuffer result = inputBuffer.asReadOnlyBuffer();
try {
if (excludeHeader)
result = skipHeader(result);
else
result = addHeader(result, type);
} catch (IOException e) {
throw new RuntimeException("Cannot skip packet header.", e);
}
return result;
}
return null;
}
/**
* Retrieve the serialized packet data (excluding the header by default) as an input stream.
* <p>
* The data is exactly the same as in {@link #getInputBuffer()}.
* @see #getInputBuffer()
* @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally.
*/
public DataInputStream getInputStream() {
return getInputStream(true);
}
/**
* Retrieve the serialized packet data as an input stream.
* <p>
* The data is exactly the same as in {@link #getInputBuffer()}.
* @see #getInputBuffer()
* @param excludeHeader - whether or not to exclude the packet ID header.
* @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally.
*/
@SuppressWarnings("resource")
public DataInputStream getInputStream(boolean excludeHeader) {
if (side.isForServer())
throw new IllegalStateException("Server-side packets have no input buffer.");
if (inputBuffer == null)
return null;
DataInputStream input = new DataInputStream(
new ByteArrayInputStream(inputBuffer.array())
);
try {
if (excludeHeader)
input = skipHeader(input);
else
input = addHeader(input, type);
} catch (IOException e) {
throw new RuntimeException("Cannot skip packet header.", e);
}
return input;
}
/**
* Whether or not the output handlers have to write a packet header.
* @return TRUE if they do, FALSE otherwise.
*/
public boolean requireOutputHeader() {
return MinecraftReflection.isUsingNetty();
}
/**
* Enqueue the given output handler for managing how the current packet will be written to the network stream.
* <p>
* Note that output handlers are not serialized, as most consumers will probably implement them using anonymous classes.
* It is not safe to serialize anonymous classes, as their name depend on the order in which they are declared in the parent class.
* <p>
* This can only be invoked on server side packet events.
* @param handler - the handler that will take part in serializing the packet.
* @return TRUE if it was added, FALSE if it has already been added.
*/
public boolean addOutputHandler(@Nonnull PacketOutputHandler handler) {
checkServerSide();
Preconditions.checkNotNull(handler, "handler cannot be NULL.");
// Lazy initialization - it's imperative that we save space and time here
if (outputHandlers == null) {
outputHandlers = new PriorityQueue<PacketOutputHandler>(10, new Comparator<PacketOutputHandler>() {
@Override
public int compare(PacketOutputHandler o1, PacketOutputHandler o2) {
return Ints.compare(o1.getPriority().getSlot(), o2.getPriority().getSlot());
}
});
}
return outputHandlers.add(handler);
}
/**
* Remove a given output handler from the serialization queue.
* <p>
* This can only be invoked on server side packet events.
* @param handler - the handler to remove.
* @return TRUE if the handler was removed, FALSE otherwise.
*/
public boolean removeOutputHandler(@Nonnull PacketOutputHandler handler) {
checkServerSide();
Preconditions.checkNotNull(handler, "handler cannot be NULL.");
if (outputHandlers != null) {
return outputHandlers.remove(handler);
}
return false;
}
/**
* Retrieve every registered output handler in no particular order.
* @return Every registered output handler.
*/
@Nonnull
public Collection<PacketOutputHandler> getOutputHandlers() {
if (outputHandlers != null) {
return outputHandlers;
} else {
return Collections.emptyList();
}
}
/**
* Add a listener that is invoked after a packet has been successfully sent to the client, or received
* by the server.
* <p>
* Received packets are not guarenteed to have been fully processed, but packets passed
* to {@link ProtocolManager#recieveClientPacket(Player, PacketContainer)} will be processed after the
* current packet event.
* <p>
* Note that post listeners will be executed asynchronously off the main thread. They are not executed
* in any defined order.
* @param listener - the listener that will be invoked.
* @return TRUE if it was added.
*/
public boolean addPostListener(PacketPostListener listener) {
if (postListeners == null)
postListeners = Lists.newArrayList();
return postListeners.add(listener);
}
/**
* Remove the first instance of the given listener.
* @param listener - listener to remove.
* @return TRUE if it was removed, FALSE otherwise.
*/
public boolean removePostListener(PacketPostListener listener) {
if (postListeners != null) {
return postListeners.remove(listener);
}
return false;
}
/**
* Retrieve an immutable view of all the listeners that will be invoked once the packet has been sent or received.
* @return Every post packet listener. Never NULL.
*/
public List<PacketPostListener> getPostListeners() {
return postListeners != null ? Collections.unmodifiableList(postListeners) : Collections.<PacketPostListener>emptyList();
}
/**
* Retrieve a list of packets that will be schedule (in-order) when the current packet has been successfully transmitted.
* <p>
* This list is modifiable.
* @return List of packets that will be scheduled.
*/
public List<ScheduledPacket> getScheduledPackets() {
if (scheduledPackets == null)
scheduledPackets = Lists.newArrayList();
return scheduledPackets;
}
/**
* Ensure that the packet event is server side.
*/
private void checkServerSide() {
if (side.isForClient()) {
throw new IllegalStateException("Must be a server side packet.");
}
}
/**
* Return a byte buffer without the header in the current packet.
* <p>
* It's safe to modify the position of the buffer.
* @param buffer - a read-only byte source.
* @return A byte buffer without the header in the current packet.
* @throws IOException If integer reading fails
*/
protected ByteBuffer skipHeader(ByteBuffer buffer) throws IOException {
skipHeader(new DataInputStream(new ByteBufferInputStream(buffer)));
return buffer;
}
/**
* Return an input stream without the header in the current packet.
* <p>
* It's safe to modify the input stream.
* @param input - input stream
* @return An input stream without the header
* @throws IOException If integer reading fails
*/
protected abstract DataInputStream skipHeader(DataInputStream input) throws IOException;
/**
* Return the byte buffer prepended with the packet header.
* @param buffer - the read-only byte buffer.
* @param type - the current packet.
* @return The byte buffer.
*/
protected abstract ByteBuffer addHeader(ByteBuffer buffer, PacketType type);
/**
* Return the input stream prepended with the packet header.
* @param input - the input stream.
* @param type - the current packet.
* @return The byte buffer.
*/
protected abstract DataInputStream addHeader(DataInputStream input, PacketType type);
/**
* Determine if the given marker has any output handlers.
* @param marker - the marker to check.
* @return TRUE if it does, FALSE otherwise.
*/
public static boolean hasOutputHandlers(NetworkMarker marker) {
return marker != null && !marker.getOutputHandlers().isEmpty();
}
/**
* Determine if the given marker has any post listeners.
* @param marker - the marker to check.
* @return TRUE if it does, FALSE otherwise.
*/
public static boolean hasPostListeners(NetworkMarker marker) {
return marker != null && !marker.getPostListeners().isEmpty();
}
/**
* Retrieve the byte buffer stored in the given marker.
* @param marker - the marker.
* @return The byte buffer, or NULL if not found.
*/
public static byte[] getByteBuffer(NetworkMarker marker) {
if (marker != null) {
ByteBuffer buffer = marker.getInputBuffer();
if (buffer != null) {
byte[] data = new byte[buffer.remaining()];
buffer.get(data, 0, data.length);
return data;
}
}
return null;
}
/**
* Retrieve the network marker of a particular event without creating it.
* <p>
* This is an internal method that should not be used by API users.
* @param event - the event.
* @return The network marker.
*/
public static NetworkMarker getNetworkMarker(PacketEvent event) {
return event.networkMarker;
}
/**
* Retrieve the scheduled packets of a particular network marker without constructing the list.
* <p>
* This is an internal method that should not be used by API users.
* @param marker - the marker.
* @return The list, or NULL if not found or initialized.
*/
public static List<ScheduledPacket> readScheduledPackets(NetworkMarker marker) {
return marker.scheduledPackets;
}
}
@@ -2,26 +2,35 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.events;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* Represents a packet listener with useful constructors.
@@ -30,29 +39,94 @@ import com.comphenix.protocol.injector.GamePhase;
* @author Kristian
*/
public abstract class PacketAdapter implements PacketListener {
protected Plugin plugin;
protected ConnectionSide connectionSide;
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
/**
* Initialize a packet adapter using a collection of parameters. Use {@link #params()} to get an instance to this builder.
* @param params - the parameters.
*/
public PacketAdapter(@Nonnull AdapterParameteters params) {
this(
checkValidity(params).plugin, params.connectionSide, params.listenerPriority,
params.gamePhase, params.options, params.packets
);
}
/**
* Initialize a packet listener with the given parameters.
* @param plugin - the plugin.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, PacketType... types) {
this(plugin, ListenerPriority.NORMAL, types);
}
/**
* Initialize a packet listener with the given parameters.
* @param plugin - the plugin.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, Iterable<? extends PacketType> types) {
this(params(plugin, Iterables.toArray(types, PacketType.class)));
}
/**
* Initialize a packet listener with the given parameters.
* @param plugin - the plugin.
* @param listenerPriority - the priority.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types) {
this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority));
}
/**
* Initialize a packet listener with the given parameters.
* @param plugin - the plugin.
* @param listenerPriority - the priority.
* @param types - the packet types.
* @param options - the options.
*/
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types, ListenerOptions... options) {
this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options));
}
/**
* Initialize a packet listener with the given parameters.
* @param plugin - the plugin.
* @param listenerPriority - the priority.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, PacketType... types) {
this(params(plugin, types).listenerPriority(listenerPriority));
}
/**
* Initialize a packet listener with default priority.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Set<Integer> packets) {
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets.toArray(new Integer[0]));
}
@@ -60,12 +134,15 @@ public abstract class PacketAdapter implements PacketListener {
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Set<Integer> packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets.toArray(new Integer[0]));
}
@@ -73,51 +150,109 @@ public abstract class PacketAdapter implements PacketListener {
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Set<Integer> packets) {
this(plugin, connectionSide, listenerPriority, gamePhase, packets.toArray(new Integer[0]));
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) {
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param options - which listener options to use.
* @param packets - the packet IDs the listener is looking for.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, GamePhase.PLAYING, packets);
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerOptions[] options, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, GamePhase.PLAYING, options, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Integer... packets) {
this(plugin, connectionSide, listenerPriority, gamePhase, new ListenerOptions[0], packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Listener options must be specified in order for {@link NetworkMarker#getInputBuffer()} to function correctly.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param options - which listener options to use.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(
Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
GamePhase gamePhase, ListenerOptions[] options, Integer... packets) {
this(plugin, connectionSide, listenerPriority, gamePhase, options,
PacketRegistry.toPacketTypes(Sets.newHashSet(packets), connectionSide.getSender()).toArray(new PacketType[0])
);
}
// For internal use only
private PacketAdapter(
Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
GamePhase gamePhase, ListenerOptions[] options, PacketType... packets) {
if (plugin == null)
throw new IllegalArgumentException("plugin cannot be null");
if (connectionSide == null)
@@ -128,17 +263,47 @@ public abstract class PacketAdapter implements PacketListener {
throw new IllegalArgumentException("gamePhase cannot be NULL");
if (packets == null)
throw new IllegalArgumentException("packets cannot be null");
if (options == null)
throw new IllegalArgumentException("options cannot be null");
ListenerOptions[] serverOptions = options;
ListenerOptions[] clientOptions = options;
// Special case that allows us to specify optionIntercept().
if (connectionSide == ConnectionSide.BOTH) {
serverOptions = except(serverOptions, new ListenerOptions[0],
ListenerOptions.INTERCEPT_INPUT_BUFFER);
}
// Add whitelists
if (connectionSide.isForServer())
sendingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
sendingWhitelist = ListeningWhitelist.newBuilder().
priority(listenerPriority).
types(packets).
gamePhase(gamePhase).
options(serverOptions).
build();
if (connectionSide.isForClient())
receivingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
receivingWhitelist = ListeningWhitelist.newBuilder().
priority(listenerPriority).
types(packets).
gamePhase(gamePhase).
options(clientOptions).
build();
this.plugin = plugin;
this.connectionSide = connectionSide;
}
// Remove a given element from an array
private static <T> T[] except(T[] values, T[] buffer, T except) {
List<T> result = Lists.newArrayList(values);
result.remove(except);
return result.toArray(buffer);
}
@Override
public void onPacketReceiving(PacketEvent event) {
// Lets prevent some bugs
@@ -168,30 +333,304 @@ public abstract class PacketAdapter implements PacketListener {
/**
* Retrieves the name of the plugin that has been associated with the listener.
* @param listener - the listener.
* @return Name of the associated plugin.
*/
public static String getPluginName(PacketListener listener) {
Plugin plugin = listener.getPlugin();
// Try to get the plugin name
return getPluginName(listener.getPlugin());
}
/**
* Retrieves the name of the given plugin.
* @param plugin - the plugin.
* @return Name of the given plugin.
*/
public static String getPluginName(Plugin plugin) {
if (plugin == null)
return "UNKNOWN";
try {
if (plugin == null)
return "UNKNOWN";
else
return plugin.getName();
return plugin.getName();
} catch (NoSuchMethodError e) {
return plugin.toString();
}
}
@Override
public String toString() {
// This is used by the error reporter
return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
getPluginName(this),
public String toString() {
// This is used by the error reporter
return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
getPluginName(this),
sendingWhitelist,
receivingWhitelist);
}
/**
* Construct a helper object for passing parameters to the packet adapter.
* <p>
* This is often simpler and better than passing them directly to each constructor.
* @return Helper object.
*/
public static AdapterParameteters params() {
return new AdapterParameteters();
}
/**
* Construct a helper object for passing parameters to the packet adapter.
* <p>
* This is often simpler and better than passing them directly to each constructor.
* Deprecated: Use {@link #params(Plugin, PacketType...)} instead.
* @param plugin - the plugin that spawned this listener.
* @param packets - the packet IDs the listener is looking for.
* @return Helper object.
*/
@Deprecated
public static AdapterParameteters params(Plugin plugin, Integer... packets) {
return new AdapterParameteters().plugin(plugin).packets(packets);
}
/**
* Construct a helper object for passing parameters to the packet adapter.
* <p>
* This is often simpler and better than passing them directly to each constructor.
* @param plugin - the plugin that spawned this listener.
* @param packets - the packet types the listener is looking for.
* @return Helper object.
*/
public static AdapterParameteters params(Plugin plugin, PacketType... packets) {
return new AdapterParameteters().plugin(plugin).types(packets);
}
/**
* Represents a builder for passing parameters to the packet adapter constructor.
* <p>
* Note: Never make spelling mistakes in a public API!
* @author Kristian
*/
public static class AdapterParameteters {
private Plugin plugin;
private ConnectionSide connectionSide;
private PacketType[] packets;
// Parameters with default values
private GamePhase gamePhase = GamePhase.PLAYING;
private ListenerOptions[] options = new ListenerOptions[0];
private ListenerPriority listenerPriority = ListenerPriority.NORMAL;
/**
* Set the plugin that spawned this listener. This parameter is required.
* @param plugin - the plugin.
* @return This builder, for chaining.
*/
public AdapterParameteters plugin(@Nonnull Plugin plugin) {
this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL.");
return this;
}
/**
* Set the packet types this listener is looking for. This parameter is required.
* @param connectionSide - the new packet type.
* @return This builder, for chaining.
*/
public AdapterParameteters connectionSide(@Nonnull ConnectionSide connectionSide) {
this.connectionSide = Preconditions.checkNotNull(connectionSide, "connectionside cannot be NULL.");
return this;
}
/**
* Set this adapter to also look for client-side packets.
* @return This builder, for chaining.
*/
public AdapterParameteters clientSide() {
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.CLIENT_SIDE));
}
/**
* Set this adapter to also look for server-side packets.
* @return This builder, for chaining.
*/
public AdapterParameteters serverSide() {
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.SERVER_SIDE));
}
/**
* Set the the event priority, where the execution is in ascending order from lowest to highest.
* <p>
* Default is {@link ListenerPriority#NORMAL}.
* @param listenerPriority - the new event priority.
* @return This builder, for chaining.
*/
public AdapterParameteters listenerPriority(@Nonnull ListenerPriority listenerPriority) {
this.listenerPriority = Preconditions.checkNotNull(listenerPriority, "listener priority cannot be NULL.");
return this;
}
/**
* Set which game phase this listener is active under. This is a hint for ProtocolLib to start intercepting login packets.
* <p>
* Default is {@link GamePhase#PLAYING}, which will not intercept login packets.
* @param gamePhase - the new game phase.
* @return This builder, for chaining.
*/
public AdapterParameteters gamePhase(@Nonnull GamePhase gamePhase) {
this.gamePhase = Preconditions.checkNotNull(gamePhase, "gamePhase cannot be NULL.");
return this;
}
/**
* Set the game phase to {@link GamePhase#LOGIN}, allowing ProtocolLib to intercept login packets.
* @return This builder, for chaining.
*/
public AdapterParameteters loginPhase() {
return gamePhase(GamePhase.LOGIN);
}
/**
* Set listener options that decide whether or not to intercept the raw packet data as read from the network stream.
* <p>
* The default is to disable this raw packet interception.
* @param options - every option to use.
* @return This builder, for chaining.
*/
public AdapterParameteters options(@Nonnull ListenerOptions... options) {
this.options = Preconditions.checkNotNull(options, "options cannot be NULL.");
return this;
}
/**
* Set listener options that decide whether or not to intercept the raw packet data as read from the network stream.
* <p>
* The default is to disable this raw packet interception.
* @param options - every option to use.
* @return This builder, for chaining.
*/
public AdapterParameteters options(@Nonnull Set<? extends ListenerOptions> options) {
Preconditions.checkNotNull(options, "options cannot be NULL.");
this.options = options.toArray(new ListenerOptions[0]);
return this;
}
/**
* Add a given option to the current builder.
* @param option - the option to add.
* @return This builder, for chaining.
*/
private AdapterParameteters addOption(ListenerOptions option) {
if (options == null) {
return options(option);
} else {
Set<ListenerOptions> current = Sets.newHashSet(options);
current.add(option);
return options(current);
}
}
/**
* Set the listener option to {@link ListenerOptions#INTERCEPT_INPUT_BUFFER}, causing ProtocolLib to read the raw packet data from the network stream.
* @return This builder, for chaining.
*/
public AdapterParameteters optionIntercept() {
return addOption(ListenerOptions.INTERCEPT_INPUT_BUFFER);
}
/**
* Set the listener option to {@link ListenerOptions#DISABLE_GAMEPHASE_DETECTION}, causing ProtocolLib to ignore automatic game phase detection.
* <p>
* This is no longer relevant in 1.7.2.
* @return This builder, for chaining.
*/
public AdapterParameteters optionManualGamePhase() {
return addOption(ListenerOptions.DISABLE_GAMEPHASE_DETECTION);
}
/**
* Set the listener option to {@link ListenerOptions#ASYNC}, indicating that our listener is thread safe.
* <p>
* This allows ProtocolLib to perform certain optimizations.
* @return This builder, for chaining.
*/
public AdapterParameteters optionAsync() {
return addOption(ListenerOptions.ASYNC);
}
/**
* Set the packet IDs of the packets the listener is looking for.
* <p>
* This parameter is required.
* <p>
* Deprecated: Use {@link #types(PacketType...)} instead.
* @param packets - the packet IDs to look for.
* @return This builder, for chaining.
*/
@Deprecated
public AdapterParameteters packets(@Nonnull Integer... packets) {
Preconditions.checkNotNull(packets, "packets cannot be NULL");
PacketType[] types = new PacketType[packets.length];
for (int i = 0; i < types.length; i++) {
types[i] = PacketType.findLegacy(packets[i]);
}
this.packets = types;
return this;
}
/**
* Set the packet IDs of the packets the listener is looking for.
* <p>
* This parameter is required.
* @param packets - a set of the packet IDs to look for.
* @return This builder, for chaining.
*/
@Deprecated
public AdapterParameteters packets(@Nonnull Set<Integer> packets) {
return packets(packets.toArray(new Integer[0]));
}
/**
* Set the packet types the listener is looking for.
* <p>
* This parameter is required.
* @param packets - the packet types to look for.
* @return This builder, for chaining.
*/
public AdapterParameteters types(@Nonnull PacketType... packets) {
// Set the connection side as well
if (connectionSide == null) {
for (PacketType type : packets) {
this.connectionSide = ConnectionSide.add(this.connectionSide, type.getSender().toSide());
}
}
this.packets = Preconditions.checkNotNull(packets, "packets cannot be NULL");
if (packets.length == 0)
throw new IllegalArgumentException("Passed an empty packet type array.");
return this;
}
/**
* Set the packet types the listener is looking for.
* <p>
* This parameter is required.
* @param packets - a set of packet types to look for.
* @return This builder, for chaining.
*/
public AdapterParameteters types(@Nonnull Set<PacketType> packets) {
return types(packets.toArray(new PacketType[0]));
}
}
/**
* Determine if the required parameters are set.
*/
private static AdapterParameteters checkValidity(AdapterParameteters params) {
if (params == null)
throw new IllegalArgumentException("params cannot be NULL.");
if (params.plugin == null)
throw new IllegalStateException("Plugin was never set in the parameters.");
if (params.connectionSide == null)
throw new IllegalStateException("Connection side was never set in the parameters.");
if (params.packets == null)
throw new IllegalStateException("Packet IDs was never set in the parameters.");
return params;
}
}
File diff suppressed because it is too large. Load diff
@@ -2,16 +2,16 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
@@ -20,26 +20,57 @@ package com.comphenix.protocol.events;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import java.util.EventObject;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import com.comphenix.protocol.Application;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
/**
* Represents a packet sending or receiving event. Changes to the packet will be
* reflected in the final version to be sent or received. It is also possible to cancel an event.
* @author Kristian
*/
public class PacketEvent extends EventObject implements Cancellable {
public static final ReportType REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING = new ReportType(
"Plugin %s changed packet type from %s to %s in packet listener. This is confusing for other plugins! (Not an error, though!)");
private static final SetMultimap<PacketType, PacketType> CHANGE_WARNINGS =
Multimaps.synchronizedSetMultimap(HashMultimap.<PacketType, PacketType>create());
/**
* Automatically generated by Eclipse.
*/
private static final long serialVersionUID = -5360289379097430620L;
private transient Player player;
private transient WeakReference<Player> playerReference;
private transient Player offlinePlayer;
private PacketContainer packet;
private boolean serverPacket;
private boolean cancel;
private AsyncMarker asyncMarker;
private boolean asynchronous;
// Network input and output handlers
NetworkMarker networkMarker;
// Whether or not a packet event is read only
private boolean readOnly;
private boolean filtered;
/**
* Use the static constructors to create instances of this event.
@@ -47,21 +78,30 @@ public class PacketEvent extends EventObject implements Cancellable {
*/
public PacketEvent(Object source) {
super(source);
this.filtered = true;
}
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
this(source, packet, null, player, serverPacket, true);
}
private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket, boolean filtered) {
super(source);
this.packet = packet;
this.player = player;
this.playerReference = new WeakReference<Player>(player);
this.networkMarker = marker;
this.serverPacket = serverPacket;
this.filtered = filtered;
}
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
super(origial.source);
this.packet = origial.packet;
this.player = origial.player;
this.playerReference = origial.playerReference;
this.cancel = origial.cancel;
this.serverPacket = origial.serverPacket;
this.filtered = origial.filtered;
this.networkMarker = origial.networkMarker;
this.asyncMarker = asyncMarker;
this.asynchronous = true;
}
@@ -77,6 +117,33 @@ public class PacketEvent extends EventObject implements Cancellable {
return new PacketEvent(source, packet, client, false);
}
/**
* Creates an event representing a client packet transmission.
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param client - the client that sent the packet.
* @return The event.
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) {
return new PacketEvent(source, packet, marker, client, false, true);
}
/**
* Creates an event representing a client packet transmission.
* <p>
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param client - the client that sent the packet.
* @param filtered - whether or not this packet event is processed by every packet listener.
* @return The event.
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client, boolean filtered) {
return new PacketEvent(source, packet, marker, client, false, filtered);
}
/**
* Creates an event representing a server packet transmission.
* @param source - the event source.
@@ -88,6 +155,33 @@ public class PacketEvent extends EventObject implements Cancellable {
return new PacketEvent(source, packet, recipient, true);
}
/**
* Creates an event representing a server packet transmission.
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param recipient - the client that will receieve the packet.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) {
return new PacketEvent(source, packet, marker, recipient, true, true);
}
/**
* Creates an event representing a server packet transmission.
* <p>
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param recipient - the client that will receieve the packet.
* @param filtered - whether or not this packet event is processed by every packet listener.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient, boolean filtered) {
return new PacketEvent(source, packet, marker, recipient, true, filtered);
}
/**
* Create an asynchronous packet event from a synchronous event and a async marker.
* @param event - the original synchronous event.
@@ -98,6 +192,19 @@ public class PacketEvent extends EventObject implements Cancellable {
return new PacketEvent(event, marker);
}
/**
* Determine if we are executing the packet event in an asynchronous thread.
* <p>
* If so, you must synchronize all calls to the Bukkit API.
* <p>
* Generally, most server packets are executed on the main thread, whereas client packets
* are all executed asynchronously.
* @return TRUE if we are, FALSE otherwise.
*/
public boolean isAsync() {
return !Application.isPrimaryThread();
}
/**
* Retrieves the packet that will be sent to the player.
* @return Packet to send to the player.
@@ -111,30 +218,100 @@ public class PacketEvent extends EventObject implements Cancellable {
* @param packet - the packet that will be sent instead.
*/
public void setPacket(PacketContainer packet) {
if (readOnly)
throw new IllegalStateException("The packet event is read-only.");
if (packet == null)
throw new IllegalArgumentException("Cannot set packet to NULL. Use setCancelled() instead.");
// Change warnings
final PacketType oldType = this.packet.getType();
final PacketType newType = packet.getType();
if (this.packet != null && !Objects.equal(oldType, newType)) {
// Only report this once
if (CHANGE_WARNINGS.put(oldType, newType)) {
ProtocolLibrary.getErrorReporter().reportWarning(this,
Report.newBuilder(REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING).
messageParam(oldType, newType).
build());
}
}
this.packet = packet;
}
/**
* Retrieves the packet ID.
* <p>
* Deprecated: Use {@link #getPacketType()} instead.
* @return The current packet ID.
*/
@Deprecated
public int getPacketID() {
return packet.getID();
}
/**
* Retrieve the packet type.
* @return The type.
*/
public PacketType getPacketType() {
return packet.getType();
}
/**
* Retrieves whether or not the packet should be cancelled.
* @return TRUE if it should be cancelled, FALSE otherwise.
*/
@Override
public boolean isCancelled() {
return cancel;
}
/**
* Retrieve the object responsible for managing the serialized input and output of a packet.
* <p>
* Note that the serialized input data is only available for client-side packets, and the output handlers
* can only be applied to server-side packets.
* @return The network manager.
*/
public NetworkMarker getNetworkMarker() {
if (networkMarker == null) {
if (isServerPacket()) {
networkMarker = new NetworkMarker.EmptyBufferMarker(
serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE);
} else {
throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener.");
}
}
return networkMarker;
}
/**
* Sets whether or not the packet should be cancelled.
* Update the network manager.
* <p>
* This method is internal - do not call.
* @param networkMarker - the new network manager.
*/
public void setNetworkMarker(NetworkMarker networkMarker) {
this.networkMarker = Preconditions.checkNotNull(networkMarker, "marker cannot be NULL");
}
/**
* Sets whether or not the packet should be cancelled. Uncancelling is possible.
* <p>
* <b>Warning</b>: A cancelled packet should never be re-transmitted. Use the asynchronous
* packet manager if you need to perform extensive processing. It should also be used
* if you need to synchronize with the main thread.
* <p>
* This ensures that other plugins can work with the same packet.
* <p>
* An asynchronous listener can also delay a packet indefinitely without having to block its thread.
*
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
*/
@Override
public void setCancelled(boolean cancel) {
if (readOnly)
throw new IllegalStateException("The packet event is read-only.");
this.cancel = cancel;
}
@@ -143,7 +320,17 @@ public class PacketEvent extends EventObject implements Cancellable {
* @return The player associated with this event.
*/
public Player getPlayer() {
return player;
return playerReference.get();
}
/**
* Determine if this packet is filtered by every packet listener.
* <p>
* If not, it will only be intercepted by monitor packets.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isFiltered() {
return filtered;
}
/**
@@ -169,21 +356,46 @@ public class PacketEvent extends EventObject implements Cancellable {
return asyncMarker;
}
/**
* Set the asynchronous marker.
* Set the asynchronous marker.
* <p>
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled
* to be processed asynchronously with the given settings.
* <p>
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
* @param asyncMarker - the new asynchronous marker, or NULL.
* @throws IllegalStateException If the current event is asynchronous.
*/
public void setAsyncMarker(AsyncMarker asyncMarker) {
if (isAsynchronous())
throw new IllegalStateException("The marker is immutable for asynchronous events");
if (readOnly)
throw new IllegalStateException("The packet event is read-only.");
this.asyncMarker = asyncMarker;
}
/**
* Determine if the current packet event is read only.
* <p>
* This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However,
* it is still possible to modify the packet itself, as it would require too many resources to verify its integrity.
* <p>
* Thus, the packet is considered immutable if the packet event is read only.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Set the read-only state of this packet event.
* <p>
* This will be reset for every packet listener.
* @param readOnly - TRUE if it is read-only, FALSE otherwise.
*/
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/**
* Determine if the packet event has been executed asynchronously or not.
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
@@ -191,24 +403,53 @@ public class PacketEvent extends EventObject implements Cancellable {
public boolean isAsynchronous() {
return asynchronous;
}
/**
* Schedule a packet for sending or receiving after the current packet event is successful.
* <p>
* The packet will be added to {@link NetworkMarker#getScheduledPackets()}.
* @param scheduled - the packet to transmit or receive.
*/
public void schedule(ScheduledPacket scheduled) {
getNetworkMarker().getScheduledPackets().add(scheduled);
}
/**
* Unschedule a specific packet.
* @param scheduled - the scheduled packet.
* @return TRUE if it was unscheduled, FALSE otherwise.
*/
public boolean unschedule(ScheduledPacket scheduled) {
if (networkMarker != null) {
return networkMarker.getScheduledPackets().remove(scheduled);
}
return false;
}
private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization
// Default serialization
output.defaultWriteObject();
// Write the name of the player (or NULL if it's not set)
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
output.writeObject(playerReference.get() != null ? new SerializedOfflinePlayer(playerReference.get()) : null);
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
input.defaultReadObject();
final SerializedOfflinePlayer offlinePlayer = (SerializedOfflinePlayer) input.readObject();
final SerializedOfflinePlayer serialized = (SerializedOfflinePlayer) input.readObject();
if (offlinePlayer != null) {
// Better than nothing
player = offlinePlayer.getPlayer();
// Better than nothing
if (serialized != null) {
// Store it, to prevent weak reference from cleaning up the reference
offlinePlayer = serialized.getPlayer();
playerReference = new WeakReference<Player>(offlinePlayer);
}
}
}
@Override
public String toString() {
return "PacketEvent[player=" + getPlayer() + ", packet=" + packet + "]";
}
}
@@ -2,16 +2,16 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
@@ -20,7 +20,7 @@ package com.comphenix.protocol.events;
import org.bukkit.plugin.Plugin;
/**
* Represents a listener that recieves notifications when packets are sent or recieved.
* Represents a listener that receives notifications when packets are sending or being received.
* <p>
* Use {@link PacketAdapter} for a simple wrapper around this interface.
* @author Kristian
@@ -32,20 +32,22 @@ public interface PacketListener {
* <p>
* Note that the packet may be replaced, if needed.
* <p>
* This method is executed on the main thread, and thus the Bukkit API is safe to use.
*
* This method is executed on the main thread in 1.6.4 and earlier, and thus the Bukkit API is safe to use.
* <p>
* In Minecraft 1.7.2 and later, this method MAY be executed asynchronously, but only if {@link ListenerOptions#ASYNC}
* have been specified in the listener. This is off by default.
* @param event - the packet that should be sent.
*/
public void onPacketSending(PacketEvent event);
/**
* Invoked right before a recieved packet from a client is being processed.
* Invoked right before a received packet from a client is being processed.
* <p>
* <b>WARNING</b>: </br>
* This method will be called <i>asynchronously</i>! You should synchronize with the main
* thread using {@link org.bukkit.scheduler.BukkitScheduler#scheduleSyncDelayedTask(Plugin, Runnable, long) scheduleSyncDelayedTask}
* <b>WARNING</b>: <br>
* This method will be called <i>asynchronously</i>! You should synchronize with the main
* thread using {@link org.bukkit.scheduler.BukkitScheduler#scheduleSyncDelayedTask(Plugin, Runnable, long) scheduleSyncDelayedTask}
* if you need to call the Bukkit API.
* @param event - the packet that has been recieved.
* @param event - the packet that has been received.
*/
public void onPacketReceiving(PacketEvent event);
@@ -0,0 +1,32 @@
package com.comphenix.protocol.events;
import org.bukkit.plugin.Plugin;
/**
* Represents an adapter version of the output handler interface.
* @author Kristian
*/
public abstract class PacketOutputAdapter implements PacketOutputHandler {
private final Plugin plugin;
private final ListenerPriority priority;
/**
* Construct a new packet output adapter with the given values.
* @param priority - the output handler priority.
* @param plugin - the owner plugin.
*/
public PacketOutputAdapter(Plugin plugin, ListenerPriority priority) {
this.priority = priority;
this.plugin = plugin;
}
@Override
public Plugin getPlugin() {
return plugin;
}
@Override
public ListenerPriority getPriority() {
return priority;
}
}
@@ -0,0 +1,37 @@
package com.comphenix.protocol.events;
import org.bukkit.plugin.Plugin;
/**
* Represents a custom packet serializer onto the network stream.
*
* @author Kristian
*/
public interface PacketOutputHandler {
/**
* Retrieve the priority that decides the order each network handler is allowed to manipulate the output buffer.
* <p>
* Higher priority is executed before lower.
* @return The handler priority.
*/
public ListenerPriority getPriority();
/**
* The plugin that owns this output handler.
* @return The owner plugin.
*/
public Plugin getPlugin();
/**
* Invoked when a given packet is to be written to the output stream.
* <p>
* Note that the buffer is initially filled with the output from the default write method.
* <p>
* In Minecraft 1.6.4, the header is always excluded, whereas it MUST be included in Minecraft 1.7.2. Call
* {@link NetworkMarker#requireOutputHeader()} to determine this.
* @param event - the packet that will be outputted.
* @param buffer - the data that is currently scheduled to be outputted.
* @return The modified byte array to write. NULL is not permitted.
*/
public byte[] handle(PacketEvent event, byte[] buffer);
}
@@ -0,0 +1,22 @@
package com.comphenix.protocol.events;
import org.bukkit.plugin.Plugin;
import com.google.common.base.Preconditions;
/**
* Represents an adapter version of a post listener.
* @author Kristian
*/
public abstract class PacketPostAdapter implements PacketPostListener {
private Plugin plugin;
public PacketPostAdapter(Plugin plugin) {
this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL");
}
@Override
public Plugin getPlugin() {
return plugin;
}
}
@@ -0,0 +1,23 @@
package com.comphenix.protocol.events;
import org.bukkit.plugin.Plugin;
/**
* Represents a packet listener that is invoked after a packet has been sent or received.
* @author Kristian
*/
public interface PacketPostListener {
/**
* Retrieve the plugin this listener belongs to.
* @return The assoicated plugin.
*/
public Plugin getPlugin();
/**
* Invoked after a packet has been sent or received.
* <p>
* Note that this is invoked asynchronously.
* @param event - the packet event.
*/
public void onPostEvent(PacketEvent event);
}
@@ -0,0 +1,147 @@
package com.comphenix.protocol.events;
import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.ProtocolLibrary;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
/**
* Represents a packet that is scheduled for transmission at a later stage.
* @author Kristian
*/
public class ScheduledPacket {
protected PacketContainer packet;
protected Player target;
protected boolean filtered;
/**
* Construct a new scheduled packet.
* <p>
* Note that the sender is infered from the packet type.
* @param packet - the packet.
* @param target - the target player.
* @param filtered - whether or not to
*/
public ScheduledPacket(PacketContainer packet, Player target, boolean filtered) {
setPacket(packet);
setTarget(target);
setFiltered(filtered);
}
/**
* Construct a new scheduled packet that will not be processed by any packet listeners (except MONITOR).
* @param packet - the packet.
* @param target - the target player.
* @return The scheduled packet.
*/
public static ScheduledPacket fromSilent(PacketContainer packet, Player target) {
return new ScheduledPacket(packet, target, false);
}
/**
* Construct a new scheduled packet that will be processed by any packet listeners.
* @param packet - the packet.
* @param target - the target player.
* @return The scheduled packet.
*/
public static ScheduledPacket fromFiltered(PacketContainer packet, Player target) {
return new ScheduledPacket(packet, target, true);
}
/**
* Retrieve the packet that will be sent or transmitted.
* @return The sent or received packet.
*/
public PacketContainer getPacket() {
return packet;
}
/**
* Set the packet that will be sent or transmitted.
* @param packet - the new packet, cannot be NULL.
*/
public void setPacket(PacketContainer packet) {
this.packet = Preconditions.checkNotNull(packet, "packet cannot be NULL");
}
/**
* Retrieve the target player.
* @return The target player.
*/
public Player getTarget() {
return target;
}
/**
* Set the target player.
* @param target - the new target, cannot be NULL.
*/
public void setTarget(Player target) {
this.target = Preconditions.checkNotNull(target, "target cannot be NULL");
}
/**
* Determine if this packet will be processed by any of the packet listeners.
* @return TRUE if it will, FALSE otherwise.
*/
public boolean isFiltered() {
return filtered;
}
/**
* Set whether or not this packet will be processed by packet listeners (except MONITOR listeners).
* @param filtered - TRUE if it should be processed by listeners, FALSE otherwise.
*/
public void setFiltered(boolean filtered) {
this.filtered = filtered;
}
/**
* Retrieve the sender of this packet.
* @return The sender.
*/
public Sender getSender() {
return packet.getType().getSender();
}
/**
* Schedule the packet transmission or reception.
*/
public void schedule() {
schedule(ProtocolLibrary.getProtocolManager());
}
/**
* Schedule the packet transmission or reception.
* @param stream - the packet stream.
*/
public void schedule(PacketStream stream) {
Preconditions.checkNotNull(stream, "stream cannot be NULL");
try {
if (getSender() == Sender.CLIENT) {
stream.recieveClientPacket(getTarget(), getPacket(), isFiltered());
} else {
stream.sendServerPacket(getTarget(), getPacket(), isFiltered());
}
} catch (InvocationTargetException e) {
throw new RuntimeException("Cannot send packet " + this + " to " + stream);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot send packet " + this + " to " + stream);
}
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("packet", packet)
.add("target", target)
.add("filtered", filtered)
.toString();
}
}
@@ -23,6 +23,7 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.sf.cglib.proxy.Enhancer;
@@ -35,6 +36,8 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.Player;
import com.comphenix.protocol.utility.EnhancerFactory;
/**
* Represents a player object that can be serialized by Java.
*
@@ -51,6 +54,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
// Relevant data about an offline player
private String name;
private UUID uuid;
private long firstPlayed;
private long lastPlayed;
private boolean operator;
@@ -75,6 +79,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
*/
public SerializedOfflinePlayer(OfflinePlayer offline) {
this.name = offline.getName();
this.uuid = offline.getUniqueId();
this.firstPlayed = offline.getFirstPlayed();
this.lastPlayed = offline.getLastPlayed();
this.operator = offline.isOp();
@@ -114,6 +119,11 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return lastPlayed;
}
@Override
public UUID getUniqueId() {
return uuid;
}
@Override
public String getName() {
return name;
@@ -208,7 +218,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}
// MORE CGLIB magic!
Enhancer ex = new Enhancer();
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
ex.setSuperclass(Player.class);
ex.setCallback(new MethodInterceptor() {
@Override
@@ -2,77 +2,120 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.google.common.primitives.Primitives;
/**
* Represents an object capable of converting wrapped Bukkit objects into NMS objects.
* <p>
* Typical conversions include:
* <ul>
* <li>org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer</li>
* <li>org.bukkit.World -> net.minecraft.server.WorldServer</li>
* <li>org.bukkit.entity.Player to net.minecraft.server.EntityPlayer</li>
* <li>org.bukkit.World to net.minecraft.server.WorldServer</li>
* </ul>
*
* @author Kristian
*/
public class BukkitUnwrapper implements Unwrapper {
private static Map<Class<?>, Method> cache = new ConcurrentHashMap<Class<?>, Method>();
private static BukkitUnwrapper DEFAULT;
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
// The current error reporter
private final ErrorReporter reporter;
/**
* Retrieve the default instance of the Bukkit unwrapper.
* @return The default instance.
*/
public static BukkitUnwrapper getInstance() {
ErrorReporter currentReporter = ProtocolLibrary.getErrorReporter();
// Also recreate the unwrapper if the error reporter has changed
if (DEFAULT == null || DEFAULT.reporter != currentReporter) {
DEFAULT = new BukkitUnwrapper(currentReporter);
}
return DEFAULT;
}
/**
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
*/
public BukkitUnwrapper() {
this(ProtocolLibrary.getErrorReporter());
}
/**
* Construct a new Bukkit unwrapper with the given error reporter.
* @param reporter - the error reporter to use.
*/
public BukkitUnwrapper(ErrorReporter reporter) {
this.reporter = reporter;
}
@SuppressWarnings("unchecked")
@Override
public Object unwrapItem(Object wrappedObject) {
// Special cases
if (wrappedObject == null) {
// Special case
if (wrappedObject == null)
return null;
} else if (wrappedObject instanceof Collection) {
Class<?> currentClass = PacketConstructor.getClass(wrappedObject);
// No need to unwrap primitives
if (currentClass.isPrimitive() || currentClass.equals(String.class))
return null;
// Next, check for types that doesn't have a getHandle()
if (wrappedObject instanceof Collection) {
return handleCollection((Collection<Object>) wrappedObject);
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
return null;
}
Class<?> currentClass = wrappedObject.getClass();
Method cachedMethod = initializeCache(currentClass);
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
try {
// Retrieve the handle
if (cachedMethod != null)
return cachedMethod.invoke(wrappedObject);
else
return null;
} catch (IllegalArgumentException e) {
// Impossible
// Retrieve the handle
if (specificUnwrapper != null)
return specificUnwrapper.unwrapItem(wrappedObject);
else
return null;
} catch (IllegalAccessException e) {
return null;
} catch (InvocationTargetException e) {
// This is REALLY bad
throw new RuntimeException("Minecraft error.", e);
}
}
// Handle a collection of items
private Object handleCollection(Collection<Object> wrappedObject) {
@SuppressWarnings("unchecked")
@@ -91,25 +134,110 @@ public class BukkitUnwrapper implements Unwrapper {
}
}
private Method initializeCache(Class<?> type) {
/**
* Retrieve a cached class unwrapper for the given class.
* @param type - the type of the class.
* @return An unwrapper for the given class.
*/
private Unwrapper getSpecificUnwrapper(final Class<?> type) {
// See if we're already determined this
if (cache.containsKey(type)) {
if (unwrapperCache.containsKey(type)) {
// We will never remove from the cache, so this ought to be thread safe
return cache.get(type);
return unwrapperCache.get(type);
}
try {
Method find = type.getMethod("getHandle");
final Method find = type.getMethod("getHandle");
// It's thread safe, as getMethod should return the same handle
cache.put(type, find);
return find;
// It's thread safe, as getMethod should return the same handle
Unwrapper methodUnwrapper = new Unwrapper() {
@Override
public Object unwrapItem(Object wrappedObject) {
try {
if (wrappedObject instanceof Class)
return checkClass((Class<?>) wrappedObject, type, find.getReturnType());
return find.invoke(wrappedObject);
} catch (IllegalArgumentException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
);
} catch (IllegalAccessException e) {
// Should not occur either
return null;
} catch (InvocationTargetException e) {
// This is really bad
throw new RuntimeException("Minecraft error.", e);
}
return null;
}
};
unwrapperCache.put(type, methodUnwrapper);
return methodUnwrapper;
} catch (SecurityException e) {
return null;
reporter.reportDetailed(this,
Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
);
} catch (NoSuchMethodException e) {
// Try getting the field unwrapper too
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
if (fieldUnwrapper != null)
return fieldUnwrapper;
else
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
}
// Default method
return null;
}
/**
* Retrieve a cached unwrapper using the handle field.
* @param type - a cached field unwrapper.
* @return The cached field unwrapper.
*/
private Unwrapper getFieldUnwrapper(final Class<?> type) {
final Field find = FieldUtils.getField(type, "handle", true);
// See if we succeeded
if (find != null) {
Unwrapper fieldUnwrapper = new Unwrapper() {
@Override
public Object unwrapItem(Object wrappedObject) {
try {
if (wrappedObject instanceof Class)
return checkClass((Class<?>) wrappedObject, type, find.getType());
return FieldUtils.readField(find, wrappedObject, true);
} catch (IllegalAccessException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
);
return null;
}
}
};
unwrapperCache.put(type, fieldUnwrapper);
return fieldUnwrapper;
} else {
// Inform about this too
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
);
return null;
}
}
private static Class<?> checkClass(Class<?> input, Class<?> expected, Class<?> result) {
if (expected.isAssignableFrom(input)) {
return result;
}
return null;
}
}
@@ -0,0 +1,512 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* A protocol manager that delays all packet listener registrations and unregistrations until
* an underlying protocol manager can be constructed.
*
* @author Kristian
*/
public class DelayedPacketManager implements InternalManager {
// Registering packet IDs that are not supported
public static final ReportType REPORT_CANNOT_SEND_QUEUED_PACKET = new ReportType("Cannot send queued packet %s.");
public static final ReportType REPORT_CANNOT_SEND_QUEUED_WIRE_PACKET = new ReportType("Cannot send queued wire packet %s.");
public static final ReportType REPORT_CANNOT_REGISTER_QUEUED_LISTENER = new ReportType("Cannot register queued listener %s.");
private volatile InternalManager delegate;
// Queued actions
private final List<Runnable> queuedActions = Collections.synchronizedList(Lists.<Runnable>newArrayList());
private final List<PacketListener> queuedListeners = Collections.synchronizedList(Lists.<PacketListener>newArrayList());
private AsynchronousManager asyncManager;
private ErrorReporter reporter;
// The current hook
private PlayerInjectHooks hook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// If we have been closed
private boolean closed;
private boolean debug;
// Queued registration
private PluginManager queuedManager;
private Plugin queuedPlugin;
private MinecraftVersion version;
public DelayedPacketManager(@Nonnull ErrorReporter reporter, @Nonnull MinecraftVersion version) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL.");
Preconditions.checkNotNull(version, "version cannot be NULL.");
this.reporter = reporter;
this.version = version;
}
/**
* Retrieve the underlying protocol manager.
* @return The underlying manager.
*/
public InternalManager getDelegate() {
return delegate;
}
@Override
public int getProtocolVersion(Player player) {
if (delegate != null)
return delegate.getProtocolVersion(player);
else
return Integer.MIN_VALUE;
}
@Override
public MinecraftVersion getMinecraftVersion() {
if (delegate != null)
return delegate.getMinecraftVersion();
else
return version;
}
/**
* Update the delegate to the underlying manager.
* <p>
* This will prompt this packet manager to immediately transmit and
* register all queued packets an listeners.
* @param delegate - delegate to the new manager.
*/
protected void setDelegate(InternalManager delegate) {
this.delegate = delegate;
if (delegate != null) {
// Update the hook if needed
if (!Objects.equal(delegate.getPlayerHook(), hook)) {
delegate.setPlayerHook(hook);
}
// Register events as well
if (queuedManager != null && queuedPlugin != null) {
delegate.registerEvents(queuedManager, queuedPlugin);
}
// And update the debug mode
delegate.setDebug(debug);
// Add any pending listeners
synchronized (queuedListeners) {
for (PacketListener listener : queuedListeners) {
try {
delegate.addPacketListener(listener);
} catch (IllegalArgumentException e) {
// Inform about this plugin error
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_QUEUED_LISTENER).
callerParam(delegate).messageParam(listener).error(e));
}
}
}
// Execute any delayed actions
synchronized (queuedActions) {
for (Runnable action : queuedActions) {
action.run();
}
}
// Don't keep this around anymore
queuedListeners.clear();
queuedActions.clear();
}
}
private Runnable queuedAddPacket(final ConnectionSide side, final Player player, final PacketContainer packet,
final NetworkMarker marker, final boolean filtered) {
return new Runnable() {
@Override
public void run() {
try {
// Attempt to send it now
switch (side) {
case CLIENT_SIDE:
delegate.recieveClientPacket(player, packet, marker, filtered);
break;
case SERVER_SIDE:
delegate.sendServerPacket(player, packet, marker, filtered);
break;
default:
throw new IllegalArgumentException("side cannot be " + side);
}
} catch (Exception e) {
// Inform about this plugin error
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_PACKET).
callerParam(delegate).messageParam(packet).error(e));
}
}
};
}
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
this.hook = playerHook;
}
@Override
public PlayerInjectHooks getPlayerHook() {
return hook;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet) throws InvocationTargetException {
sendServerPacket(receiver, packet, null, true);
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters) throws InvocationTargetException {
sendServerPacket(receiver, packet, null, filters);
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
if (delegate != null) {
delegate.sendServerPacket(receiver, packet, marker, filters);
} else {
queuedActions.add(queuedAddPacket(ConnectionSide.SERVER_SIDE, receiver, packet, marker, filters));
}
}
@Override
public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException {
WirePacket packet = new WirePacket(id, bytes);
sendWirePacket(receiver, packet);
}
@Override
public void sendWirePacket(final Player receiver, final WirePacket packet) throws InvocationTargetException {
if (delegate != null) {
delegate.sendWirePacket(receiver, packet);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
try {
delegate.sendWirePacket(receiver, packet);
} catch (Throwable ex) {
// Inform about this plugin error
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_WIRE_PACKET)
.callerParam(delegate)
.messageParam(packet)
.error(ex));
}
}
});
}
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, null, true);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, null, filters);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) throws IllegalAccessException, InvocationTargetException {
if (delegate != null) {
delegate.recieveClientPacket(sender, packet, marker, filters);
} else {
queuedActions.add(queuedAddPacket(ConnectionSide.CLIENT_SIDE, sender, packet, marker, filters));
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet, final Entity entity, final boolean includeTracker) {
if (delegate != null) {
delegate.broadcastServerPacket(packet, entity, includeTracker);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet, entity, includeTracker);
}
});
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet, final Location origin, final int maxObserverDistance) {
if (delegate != null) {
delegate.broadcastServerPacket(packet, origin, maxObserverDistance);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet, origin, maxObserverDistance);
}
});
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet) {
if (delegate != null) {
delegate.broadcastServerPacket(packet);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet);
}
});
}
}
@Override
public ImmutableSet<PacketListener> getPacketListeners() {
if (delegate != null)
return delegate.getPacketListeners();
else
return ImmutableSet.copyOf(queuedListeners);
}
@Override
public void addPacketListener(PacketListener listener) {
if (delegate != null)
delegate.addPacketListener(listener);
else
queuedListeners.add(listener);
}
@Override
public void removePacketListener(PacketListener listener) {
if (delegate != null)
delegate.removePacketListener(listener);
else
queuedListeners.remove(listener);
}
@Override
public void removePacketListeners(Plugin plugin) {
if (delegate != null) {
delegate.removePacketListeners(plugin);
} else {
for (Iterator<PacketListener> it = queuedListeners.iterator(); it.hasNext(); ) {
// Remove listeners of the same plugin
if (Objects.equal(it.next().getPlugin(), plugin)) {
it.remove();
}
}
}
}
@Override
@Deprecated
public PacketContainer createPacket(int id) {
if (delegate != null)
return delegate.createPacket(id);
return createPacket(id, true);
}
@Override
@Deprecated
public PacketContainer createPacket(int id, boolean forceDefaults) {
if (delegate != null) {
return delegate.createPacket(id);
} else {
// Fallback implementation
PacketContainer packet = new PacketContainer(id);
// Use any default values if possible
if (forceDefaults) {
try {
packet.getModifier().writeDefaults();
} catch (FieldAccessException e) {
throw new RuntimeException("Security exception.", e);
}
}
return packet;
}
}
@SuppressWarnings("deprecation")
@Override
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
if (delegate != null)
return delegate.createPacketConstructor(id, arguments);
else
return PacketConstructor.DEFAULT.withPacket(id, arguments);
}
@Override
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments) {
if (delegate != null)
return delegate.createPacketConstructor(type, arguments);
else
return PacketConstructor.DEFAULT.withPacket(type, arguments);
}
@Override
@Deprecated
public Set<Integer> getSendingFilters() {
if (delegate != null) {
return delegate.getSendingFilters();
} else {
// Linear scan is fast enough here
Set<Integer> sending = Sets.newHashSet();
for (PacketListener listener : queuedListeners) {
sending.addAll(listener.getSendingWhitelist().getWhitelist());
}
return sending;
}
}
@Override
@Deprecated
public Set<Integer> getReceivingFilters() {
if (delegate != null) {
return delegate.getReceivingFilters();
} else {
Set<Integer> recieving = Sets.newHashSet();
for (PacketListener listener : queuedListeners) {
recieving.addAll(listener.getReceivingWhitelist().getWhitelist());
}
return recieving;
}
}
@Override
public PacketContainer createPacket(PacketType type) {
return createPacket(type.getLegacyId());
}
@Override
public PacketContainer createPacket(PacketType type, boolean forceDefaults) {
return createPacket(type.getLegacyId(), forceDefaults);
}
@Override
public Set<PacketType> getSendingFilterTypes() {
return PacketRegistry.toPacketTypes(getSendingFilters(), Sender.SERVER);
}
@Override
public Set<PacketType> getReceivingFilterTypes() {
return PacketRegistry.toPacketTypes(getReceivingFilters(), Sender.CLIENT);
}
@Override
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
if (delegate != null)
delegate.updateEntity(entity, observers);
else
EntityUtilities.updateEntity(entity, observers);
}
@Override
public Entity getEntityFromID(World container, int id) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityFromID(container, id);
else
return EntityUtilities.getEntityFromID(container, id);
}
@Override
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityTrackers(entity);
else
return EntityUtilities.getEntityTrackers(entity);
}
@Override
public boolean isClosed() {
return closed || (delegate != null && delegate.isClosed());
}
@Override
public AsynchronousManager getAsynchronousManager() {
if (delegate != null)
return delegate.getAsynchronousManager();
else
return asyncManager;
}
@Override
public boolean isDebug() {
return debug;
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;
if (delegate != null) {
delegate.setDebug(debug);
}
}
/**
* Update the asynchronous manager. This must be set.
* @param asyncManager - the asynchronous manager.
*/
public void setAsynchronousManager(AsynchronousManager asyncManager) {
this.asyncManager = asyncManager;
}
@Override
public void registerEvents(PluginManager manager, Plugin plugin) {
if (delegate != null) {
delegate.registerEvents(manager, plugin);
} else {
queuedManager = manager;
queuedPlugin = plugin;
}
}
@Override
public void close() {
if (delegate != null)
delegate.close();
closed = true;
}
}
@@ -1,3 +1,20 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector;
import org.bukkit.plugin.Plugin;
Loaded 100 of 359 files, more files were not shown because too many files have changed in this diff. Show more