Compare commits

...
1043 Commits
Author SHA1 Message Date
Dan Mulloy 3f31af6b70 I'll look into signing releases later 2016-12-21 14:20:56 -05:00
Dan Mulloy b6a054b766 Release version 4.2.0 2016-12-21 14:16:57 -05:00
Dan Mulloy 3b7b692e80 Update to 1.11.2 2016-12-21 14:02:32 -05:00
Dan Mulloy c6788addeb Mark 1.11.1 as officially supported 2016-12-20 23:37:35 -05:00
Dan Mulloy 5b38bc7f38 Update to 1.11.1 2016-12-20 16:14:13 -05:00
Dan Mulloy 33bb12713d Restore ItemStack to CraftItemStack conversion 2016-12-16 12:36:35 -05:00
Dan Mulloy d175a7655e Refactor package caching, account for some edge cases 2016-11-29 19:39:11 -05:00
Dan Mulloy 4afb38aae1 Merge pull request #115 from Mast3rPlan/master
Fix performance issue
2016-11-27 12:41:02 -05:00
Emiel Tasseel 36b5670b3d Fix performance issue where classes that don't exist in the current environment endlessly get looked up over and over again 2016-11-26 12:50:27 +01:00
Dan Mulloy 7b61796506 Add mechanism to clone NonNullLists 2016-11-22 17:36:49 -05:00
Dan Mulloy 684b687e42 Rework ItemStack handling for 1.11
Note to developers: You should never supply a null ItemStack to any
method. ProtocolLib will never return a null ItemStack. Update your
plugins accordingly.
2016-11-20 16:29:23 -05:00
Dan Mulloy 7a9b9afe2e Add modifier for item lists 2016-11-19 19:18:35 -05:00
Dan Mulloy 7d382279ad Update to Minecraft 1.11 2016-11-16 21:56:46 -05:00
Dan Mulloy b4f5f18283 Strongly recommend Java 8
If you must use Java 7, set 'ignoreJava: true' in the config.
2016-10-14 19:28:17 -04:00
Dan Mulloy 17e150393a Avoid throwing errors with invalid entities 2016-10-14 19:20:47 -04:00
Dan Mulloy b5ecfb9c5f Update version to 4.2.0-SNAPSHOT 2016-10-14 19:20:16 -04:00
Dan Mulloy 31a0c9b10c Update version in plugin.yml 2016-09-18 15:27:49 -04:00
Dan Mulloy e4d95e09cf Update version to 4.1.0 for release 2016-09-18 15:27:01 -04:00
Dan Mulloy d173be0db5 Actually use UpdateSign when it exists 2016-09-18 14:42:40 -04:00
Dan Mulloy 051989a396 Merge pull request #265 from faradize/master
Added the ability of reading/writing lists of NBT classes!
2016-09-11 17:00:25 -04:00
Dan Mulloy 795c77e943 Fix minor example issue
Closes aadnk#113
2016-09-11 16:56:05 -04:00
Dan Mulloy 1de6bbc9dc Make sure we use the update sign packet when it exists
Should fix #263
2016-09-11 16:52:45 -04:00
verbuchselt 474c74b1f9 Added the ability of reading/writing lists of NBT classes!
Works perfectly with MapChunk-Packet's TileEntity-Data!
2016-09-07 11:37:30 +02:00
Dan Mulloy caaa57c915 Add basic TileEntityTest 2016-08-27 13:08:22 -04:00
Dan Mulloy 588f736348 Fix missing TileEntity write methods
Fixes #249
2016-08-27 13:07:49 -04:00
Dan Mulloy 828302150a Deal with plugins that create proxy player objects
As I was writing this I realized it sounded like Bungee support, but it
isn't
2016-08-13 13:19:13 -04:00
Dan Mulloy 144723af18 Ensure we're always using leniency with components
Fixes #252
2016-08-06 14:40:49 -04:00
Dan Mulloy 087913ab3a Standardize equals and hashCode for wrappers 2016-08-06 13:19:14 -04:00
Dan Mulloy 64942cbb5e Make much of ProtocolLib locale independent
Fixes #242
2016-07-25 14:09:23 -04:00
Dan Mulloy 97430a8e42 Add renamed OPEN_SIGN_ENTITY packet for backwards compat
Fixes #243
2016-07-25 13:47:56 -04:00
Dan Mulloy 1bdd200794 Try to fix a weird null case 2016-07-16 16:03:01 -04:00
Dan Mulloy 679035c3bf Fix an error with wire packets
Addresses #224
2016-07-16 15:46:23 -04:00
Dan Mulloy bd4352f041 Fix cloning data watchers
Addresses #224
2016-07-12 16:15:46 -04:00
Dan Mulloy 05ffeb8e7f Restore compatibility with 1.8
1.8.8 is still one of the most popular server versions. As a result,
many servers were not receiving important fixes and new APIs.

I was able to accomplish this with minimal bloat, making it worth it.

Upon release, 4.1.0 will become the recommended version for 1.8 thru the
current Spigot build.
2016-07-12 13:37:55 -04:00
Dan Mulloy 6c982a83f0 Cache null classes
This should prevent multiple unnecessary calls to the expensive
loadClass method

Fixes #236
2016-07-09 15:32:35 -04:00
Dan Mulloy 9860fbaec6 Fix API versioning 2016-07-07 12:19:10 -04:00
Dan Mulloy 26c29d9c85 Add conversion methods to WirePacket 2016-07-06 15:47:43 -04:00
Dan Mulloy 242b25eb1e Wrap all serializable objects in data watchers 2016-07-06 15:47:24 -04:00
Dan Mulloy c13b8d4fc6 Add modifiers for UUIDs and Directions 2016-07-06 15:46:42 -04:00
Dan Mulloy 12e9c08a1a Update version to 4.1.0-SNAPSHOT 2016-07-06 15:44:41 -04:00
Dan Mulloy 98a0528806 Update version to 4.0.2 for release 2016-07-06 13:42:57 -04:00
Dan Mulloy 015a6067fd Replace some debug info with the actual cause 2016-07-05 22:54:25 -04:00
Dan Mulloy e34105e754 Rework the component array modifier to work in 1.9.4 and up
Fixes #215
2016-07-03 13:12:24 -04:00
Dan Mulloy a995866d5b Add block data to the cloning mechanism 2016-06-30 12:34:13 -04:00
Dan Mulloy a88347dfe6 Add some more specific error messages for entity tracking
Addresses #229
2016-06-30 12:22:48 -04:00
Dan Mulloy 4f404a41c6 Update to 1.10.2
No changes, yay!
2016-06-26 23:06:00 -04:00
Dan Mulloy 88444dcf74 Fix another entity issue with Paper
Fixes #220
2016-06-20 20:50:26 -04:00
Dan Mulloy 4f5ab9eb8c Wrap and unwrap values in Optionals 2016-06-20 20:41:40 -04:00
Dan Mulloy f51427b63c Update to 1.10 2016-06-08 22:28:53 -04:00
Dan Mulloy cdcb6a6007 Update version to 4.0.2-SNAPSHOT 2016-06-08 11:12:46 -04:00
Dan Mulloy ac7e17d0a6 Update version to 4.0.1 for release 2016-05-22 17:31:42 -04:00
Dan Mulloy 402390866a Fix a few issues with debug logging
Also add a warning if the updater section is missing
2016-05-22 17:26:36 -04:00
Dan Mulloy cc362a1b7f Ensure the modification count is checked correctly
Fixes #202
2016-05-21 17:44:25 -04:00
Dan Mulloy 1ca7973b77 Correct line endings
Also ensure every file ends with a new line for consistency
2016-05-21 17:37:13 -04:00
Dan Mulloy 9ecbea68ca Disable auto download by default 2016-05-21 16:04:57 -04:00
Dan Mulloy e5bc602af3 Fix a few issues with the registry 2016-05-18 21:03:14 -04:00
Dan Mulloy a9aa406d29 Missing return statement
Also, why is git showing whole-class diffs? Must be the dang line
endings again
2016-05-18 20:48:59 -04:00
Dan Mulloy c1ae6f14fc Add some debug info for #202
Also /really/ make sure it's only called once
2016-05-18 20:47:08 -04:00
Dan Mulloy 411b7a2446 Catch the right exception 2016-05-17 17:04:19 -04:00
Dan Mulloy 869b457810 Fix a false positive with the updater 2016-05-16 17:43:58 -04:00
Dan Mulloy 4330bae47f Add some debug info for #208 2016-05-16 16:27:05 -04:00
Dan Mulloy 1c36c41050 Update version to 4.0.1-SNAPSHOT 2016-05-16 16:26:40 -04:00
Dan Mulloy bea4356aca Update version to 4.0.0 for release 2016-05-15 17:43:20 -04:00
0x277F 32f51c612a Add contributing guidelines and issue template 2016-05-14 17:12:51 -04:00
Dan Mulloy 355d00d951 Complete 1.9.4 update
Fixes #204
2016-05-13 17:12:21 -04:00
Dan Mulloy bf56f3919f Merge pull request #205 from 0x277F/mc-1.9.4
Update to 1.9.4
2016-05-13 16:52:50 -04:00
0x277F d876580ddf Update to 1.9.4 2016-05-12 16:30:49 -06:00
Dan Mulloy 90f36198d2 Add plugin caller to setPacket warning
Fixes #201
2016-05-02 22:04:10 -04:00
Dan Mulloy e542f3c9d3 Add back a missing data watcher constructor
Also bolstered some of the unit tests
Fixes #191
2016-04-16 21:24:58 -04:00
Dan Mulloy 87c56d0248 We support 1.9.2 2016-04-10 21:28:52 -04:00
Dan Mulloy 8c928cb237 Allow the usage of user-created enums
Basically this will allow developers to create their own enums instead
of having to wait on me to make them. The only caveat is that enum
constants will have to match up exactly with their NMS counterparts
2016-04-06 22:55:14 -04:00
Dan Mulloy 6030992af4 Add modifiers for Sounds and Hands 2016-04-04 21:37:45 -04:00
Dan Mulloy 688da1a1bb Add protocol version for 1.9.2 2016-04-03 00:49:04 -04:00
Dan Mulloy ceecd51982 Update to 1.9.2 2016-04-02 23:01:19 -04:00
Dan Mulloy c77a700442 Add wrapper for ItemSlot
Closes #183
2016-04-02 14:54:31 -04:00
Dan Mulloy 10ded26d60 Documentation improvements 2016-04-02 14:47:54 -04:00
Dan Mulloy 74adaba8ac Update packet types and IDs for 1.9
A few packet names have changed, they're now deprecated
2016-03-20 17:46:23 -04:00
Dan Mulloy 19c57b0e5e Restore disabled tests, fix block data 2016-03-20 13:35:59 -04:00
Dan Mulloy f561057311 Fix an error with TinyProtocol reflection
Fixes aadnk#103
2016-03-19 22:36:25 -04:00
Dan Mulloy 0e6a7a39a0 Add modifiers for SoundCategory and MobEffectList
Fixes #156
2016-03-19 22:30:01 -04:00
Dan Mulloy 13b905e762 Fix a few issues with loading 2016-03-19 16:41:02 -04:00
Dan Mulloy b4c0cc5a27 Split API off into a separate module 2016-03-19 16:01:38 -04:00
Dan Mulloy a042c8bb3c Update the dig type enum for 1.9 2016-03-12 15:54:57 -05:00
Dan Mulloy 286a642739 Fix false update notifications with beta releases
Fixes #173
2016-03-12 15:51:12 -05:00
Dan Mulloy 513a83c4b0 Fix block positions in watchers and issues with components
Fixes #170
2016-03-12 15:35:34 -05:00
Dan Mulloy 9b2c8826b1 Fix a typo
That wouldn't be a terrible name for the class though
2016-03-11 14:15:14 -05:00
Dan Mulloy 413a3c27a4 Fix an issue with deep cloning data watchers
Fixes #172
2016-03-11 14:06:43 -05:00
Dan Mulloy c9328d98d7 Return to normal versioning 2016-03-09 22:02:35 -05:00
Dan Mulloy f6c0751ce3 Update version to 3.7.0-BETA for beta release 2016-03-07 19:08:16 -05:00
Dan Mulloy 16c7ebef73 A few minor tweaks 2016-03-07 19:05:58 -05:00
Dan Mulloy ec38ac7292 Revert changes to EntityUtilities
Citizens fixed the problem on their end
2016-03-06 18:32:39 -05:00
Dan Mulloy b8497a91a6 Disable updater tests
They're too prone to failure due to HTTP errors
2016-03-06 14:11:56 -05:00
Dan Mulloy 5115bcaf98 Add an optional cloner, fix sound cloning, clean up the data watcher 2016-03-06 14:02:45 -05:00
Dan Mulloy 7077037bb7 The serializer can be null
But this will cause issues when trying to register objects
2016-03-05 23:53:55 -05:00
Dan Mulloy 11ba322b5f Fix an issue with cloning metadata packets
Also improved some documentation and tests
2016-03-05 23:28:48 -05:00
Dan Mulloy a39a37ec51 Allow new entries to be created in the data watcher
Addresses #160
2016-03-05 20:36:54 -05:00
Dan Mulloy ec8fa0d1fe Add wrapper for MinecraftKey, more work on data watchers 2016-03-04 21:24:12 -05:00
Dan Mulloy 5efc4ffee3 Update player actions for 1.9
Fixes #161
2016-03-04 17:32:43 -05:00
Dan Mulloy c2d028df4f Fix a few issues with data watchers 2016-03-04 17:16:50 -05:00
Dan Mulloy 9b838df17d Fix a few errors with data watchers
This is why we have unit tests lol
2016-03-03 21:27:05 -05:00
Dan Mulloy a20ff4645d Add wrappers for data watcher objects, serializers, and the registry
Thanks @NavidK0 for help with the design
2016-03-03 21:18:51 -05:00
Dan Mulloy 850e3431da Restore a few more methods, add some documentation 2016-03-02 20:47:00 -05:00
Dan Mulloy 072a60516e Update particles for 1.9 2016-03-02 18:48:47 -05:00
Dan Mulloy 9b3feb995a Restore some methods to the data watcher wrappers 2016-03-02 18:27:12 -05:00
Dan Mulloy d359f7455f [Breaking] Update data watchers for 1.9
Basically I had to rewrite the classes since Mojang decided to muck the
whole thing up. If there are any methods necessary, I'll try to readd
them.
2016-03-02 18:08:01 -05:00
Dan Mulloy 4987cd188a Comment out data watcher exception
There's a chance that the fallbacks will actually work properly, it's
worth a try until I can find a permanent solution.
2016-03-01 18:34:35 -05:00
Dan Mulloy 019e45572d 1.9 update: Fix entity modifiers
Fixes #153
2016-03-01 16:07:59 -05:00
Dan Mulloy d87e877a27 Update internal versioning for 1.9
It looks like most things are stable at this point, just have to figure
out data watchers
2016-02-29 19:15:54 -05:00
Dan Mulloy 05c7da17b9 1.9 update: It compiles!
Note: This is an untested build, use with caution!
2016-02-29 18:55:29 -05:00
Dan Mulloy 8c21f75e6d Update readme links 2016-02-27 22:48:54 -05:00
Dan Mulloy 615a4e6e37 Use the correct Gson class in 1.7.10 2016-02-27 22:48:11 -05:00
Dan Mulloy 11ca1bf51e Merge pull request #150 from iNevet/master
Add the actual creation of the packet
2016-02-25 13:51:15 -05:00
iNevet 7fabfad65d Better code example 2016-02-23 15:28:14 +08:00
Dan Mulloy ce4779f49d Revert "Fix spelling mistake in PacketAdapter"
This reverts commit cc4d035b63.
2016-02-19 12:56:57 -05:00
Dan Mulloy a88b919e60 Make update checking more robust 2016-02-17 21:48:24 -05:00
Dan Mulloy cc4d035b63 Fix spelling mistake in PacketAdapter
Fixes #113

Thanks @Sheigutn and @NavidK0
2016-02-17 21:10:25 -05:00
Dan Mulloy 12b78eb1d7 Switch to Java 7
This shouldn't be an issue as Java 6 has been EOL'd for years now. Heck,
even Java 7 has been EOL'd

Also updated maven plugin versions
2016-02-17 21:03:26 -05:00
Dan Mulloy 8a5267ab76 Update version to 3.7-SNAPSHOT 2016-02-17 20:52:11 -05:00
Dan Mulloy a189947617 Fix issue with entity tracking on PaperSpigot 2016-02-16 15:43:23 -05:00
Dan Mulloy f01b3999ff Update version to 3.6.6-SNAPSHOT 2016-02-16 15:41:55 -05:00
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 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
368 changed files with 58878 additions and 7440 deletions

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
Contributing to ProtocolLib
===========================
This page will detail specific things that must be done if you intend to contribute to this project.
## Updating ProtocolLib to a New Protocol Version
#### Before we get started:
1. **When do we need to update the protocol version?**
_A Minecraft protocol version is a number for each specification of the Minecraft protocol.
This means it is incremented when there is a change to the [Protocol Specification](http://wiki.vg/Protocol),
but not necessarily for each Minecraft version update. If the latest Minecraft version contains a protocol change,
that means ProtocolLib needs to be updated._
2. **When do we need to update other parts of ProtocolLib?**
_When the package version guard changes. The package version guard is the version string inserted
into the package identifier for CraftBukkit and net.minecraft.server (e.g. v1_9_R2). It is incremented
by Spigot if there is a major breaking change in the internal server code. This is to encourage plugins
that interact directly with CraftBukkit/nms code to update more specifically. However, since most of
ProtocolLib uses reflection, and the structure of packet classes is generally static, one will have
to go through the code and change imports from nms/CraftBukkit._
#### Ready? Let's get started!
1. Read the [Protocol Changes](http://wiki.vg/Protocol_History). Always make sure the list is both
complete and correct. If you're unsure, don't hesitate to ask in #mcdevs (the people who maintain wiki.vg) on [freenode.net](http://freenode.net)
or #spigot on [irc.spi.gt](http://irc.spi.gt) ([webchat](https://irc.spi.gt/iris/?channels=spigot)).
2. Search for usages of the now-defunct NMS package guard and change them.
3. The class `com.comphenix.protocol.PacketType` contains a list of all the packets. If any packets were added or removed
(or had their ID changed), make sure to update this list. If a packet was removed
in favor of usage of another packet, instead of removing it, move it to the bottom of the list
in its section, add a deprecation warning to it, and redirect it to the packet that replaced it.
4. `mvn` in the root directory to build the project.
5. If tests fail in the maven build, go through the tests to make sure you removed references to any removed packets
and changed the NMS version guards.
6. Increment the package version in `com.comphenix.protocol.utility.Constants`.
7. `com.comphenix.protocol.ProtocolLibrary` contains several constants that must be updated, including the Minecraft version
and the release date.
8. `com.comphenix.protocol.utility.MinecraftProtocolVersion` contains a map of all the protocol version integers.
If the protocol version has been incremented, add a new line to the map.
9. `mvn` in root directory again. If it builds successfully, test on the appropriate version of a Spigot server. If
the build fails, debug!
+10
View File
@@ -0,0 +1,10 @@
_Follow this template except for feature requests. Use pastebin when providing /protocol dump and any relevant errors._
Make sure you've done the following:
- [ ] You're using the latest build for your server version
- [ ] This isn't an issue caused by another plugin
- [ ] You've checked for duplicate issues
Debug paste link:
Description and relevant errors:
+178
View File
@@ -0,0 +1,178 @@
#################
## Eclipse
#################
# Generic project files
.project
.classpath
.settings/
bin/
.idea/
.iml
.ipr
.iws
*.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
#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
-72
View File
@@ -1,72 +0,0 @@
<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.itemdisguise</groupId>
<artifactId>ItemDisguise</artifactId>
<version>1.0.0</version>
<name>Item Disguise</name>
<description>Change the appearance of inventory items.</description>
<properties>
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>bukkit-rep</id>
<url>http://repo.bukkit.org/content/groups/public</url>
</repository>
<repository>
<id>comphenix-rep</id>
<name>Comphenix Maven Releases</name>
<url>http://repo.comphenix.net/content/groups/public</url>
</repository>
</repositories>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<directory>src/main/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<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>
<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>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.3.2-R1.0</version>
</dependency>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</project>
@@ -1,122 +0,0 @@
/*
* ItemDisguise - A simple Bukkit plugin that illustrates how to use ProtocolLib.
* 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.itemdisguise;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.FieldAccessException;
public class HideEnchantmentsListener {
private final Server server;
private final Logger logger;
public HideEnchantmentsListener(Server server, Logger logger) {
this.server = server;
this.logger = logger;
}
public void addListener(ProtocolManager protocolManager, JavaPlugin plugin) {
// Hide all enchantments
protocolManager.addPacketListener(new PacketAdapter(plugin, ConnectionSide.SERVER_SIDE, 0x67, 0x68, 0x3E) {
@Override
public void onPacketSending(PacketEvent event) {
PacketContainer packet = event.getPacket();
try {
// Item packets
switch (event.getPacketID()) {
case 0x67: // Set slot
removeEnchantments(packet.getItemModifier().read(0));
break;
case 0x68: // Set Window Items
ItemStack[] elements = packet.getItemArrayModifier().read(0);
for (int i = 0; i < elements.length; i++) {
if (elements[i] != null) {
removeEnchantments(elements[i]);
}
}
break;
case 0x3E: // Sound effect
event.setCancelled(true);
break;
}
} catch (FieldAccessException e) {
logger.log(Level.SEVERE, "Couldn't access field.", e);
}
}
});
// Censor
protocolManager.addPacketListener(new PacketAdapter(plugin, ConnectionSide.CLIENT_SIDE, 0x3) {
@Override
public void onPacketReceiving(PacketEvent event) {
if (event.getPacketID() == 0x3) {
try {
String message = event.getPacket().
getSpecificModifier(String.class).read(0);
if (message.contains("shit") || message.contains("fuck")) {
event.setCancelled(true);
event.getPlayer().sendMessage("Bad manners!");
}
} catch (FieldAccessException e) {
logger.log(Level.SEVERE, "Couldn't access field.", e);
}
}
}
});
}
public Server getServer() {
return server;
}
public void removeListener(ProtocolManager protocolManager, JavaPlugin plugin) {
// Just remove every adapter with this plugin
protocolManager.removePacketListeners(plugin);
}
private void removeEnchantments(ItemStack stack) {
if (stack == null)
return;
Object[] copy = stack.getEnchantments().keySet().toArray();
for (Object enchantment : copy) {
stack.removeEnchantment((Enchantment) enchantment);
}
}
}
@@ -1,99 +0,0 @@
/*
* ItemDisguise - A simple Bukkit plugin that illustrates how to use ProtocolLib.
* 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.itemdisguise;
import java.util.logging.Logger;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketContainer;
public class ItemDisguiseMod extends JavaPlugin {
private ProtocolManager protocolManager;
private Logger logger;
private HideEnchantmentsListener enchantmentsListener;
@Override
public void onEnable() {
logger = getLoggerSafely();
protocolManager = ProtocolLibrary.getProtocolManager();
enchantmentsListener = new HideEnchantmentsListener(getServer(), logger);
enchantmentsListener.addListener(protocolManager, this);
}
@Override
public void onDisable() {
enchantmentsListener.removeListener(protocolManager, this);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player) {
Player player = (Player) sender;
if (label.equalsIgnoreCase("explosion")) {
PacketContainer fakeExplosion = protocolManager.createPacket(0x3C);
// Set the coordinates
try {
fakeExplosion.getSpecificModifier(double.class).
write(0, player.getLocation().getX()).
write(1, player.getLocation().getY()).
write(2, player.getLocation().getZ());
fakeExplosion.getSpecificModifier(float.class).
write(0, 3.0F);
protocolManager.sendServerPacket(player, fakeExplosion);
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
return false;
}
// 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;
}
}
-14
View File
@@ -1,14 +0,0 @@
name: ItemDisguiseMod
version: 1.0.0
description: Change the appearance of inventory items.
author: Comphenix
website: http://www.comphenix.net/ItemDisguise
main: com.comphenix.itemdisguise.ItemDisguiseMod
depends: [ProtocolLib]
database: false
commands:
explosion:
description: Creates a fake explosion around the caller.
usage: /<command>
-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>
-1
View File
@@ -1 +0,0 @@
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.5.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>
-215
View File
@@ -1,215 +0,0 @@
<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.5.0</version>
<packaging>jar</packaging>
<description>Provides read/write access to the Minecraft protocol.</description>
<properties>
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
</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>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<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>
<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>
<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>
</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>
</scm>
<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>
<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>
<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>
</dependencies>
</project>
@@ -1,72 +0,0 @@
/*
* 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 org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer;
/**
* Represents a object capable of sending or receiving packets.
*
* @author Kristian
*/
public interface PacketStream {
/**
* Send a packet to the given player.
* @param reciever - the reciever.
* @param packet - packet to send.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player reciever, PacketContainer packet)
throws InvocationTargetException;
/**
* Send a packet to the given player.
* @param reciever - the reciever.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
throws InvocationTargetException;
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
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.
* @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;
}
@@ -1,259 +0,0 @@
/*
* 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.IOException;
import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.MonitorAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.DelayedSingleTask;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
/**
* The main entry point for ProtocolLib.
*
* @author Kristian
*/
public class ProtocolLibrary extends JavaPlugin {
// There should only be one protocol manager, so we'll make it static
private static PacketFilterManager protocolManager;
// Information logger
private Logger logger;
// Error reporter
private ErrorReporter reporter;
// Metrics and statistisc
private Statistics statistisc;
// 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;
private int tickCounter = 0;
private static final int ASYNC_PACKET_DELAY = 1;
// Used to unhook players after a delay
private DelayedSingleTask unhookTask;
// Used for debugging
private boolean debugListener;
@Override
public void onLoad() {
// Add global parameters
DetailedErrorReporter reporter = new DetailedErrorReporter();
try {
logger = getLoggerSafely();
unhookTask = new DelayedSingleTask(this);
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, reporter);
reporter.addGlobalParameter("manager", protocolManager);
} catch (Throwable e) {
reporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager);
}
}
@Override
public void onEnable() {
try {
Server server = getServer();
PluginManager manager = server.getPluginManager();
// Initialize background compiler
if (backgroundCompiler == null) {
backgroundCompiler = new BackgroundCompiler(getClassLoader());
BackgroundCompiler.setInstance(backgroundCompiler);
}
// 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();
} catch (Throwable e) {
reporter.reportDetailed(this, "Cannot enable ProtocolLib.", e);
disablePlugin();
return;
}
// Try to enable statistics
try {
statistisc = new Statistics(this);
} catch (IOException e) {
reporter.reportDetailed(this, "Unable to enable metrics.", e, statistisc);
} catch (Throwable e) {
reporter.reportDetailed(this, "Metrics cannot be enabled. Incompatible Bukkit version.", e, statistisc);
}
}
/**
* Toggle a listener that prints every sent and received packet.
*/
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();
logger.info(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();
logger.info(String.format(
"SENDING %s@%s from %s.",
handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName()
));
}
});
}
debugListener = !debugListener;
}
/**
* Disable the current plugin.
*/
private void disablePlugin() {
getServer().getPluginManager().disablePlugin(this);
}
private void createAsyncTask(Server server) {
try {
if (asyncPacketTask >= 0)
throw new IllegalStateException("Async task has already been created");
// Attempt to create task
asyncPacketTask = 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);
}
}, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY);
} catch (Throwable e) {
if (asyncPacketTask == -1) {
reporter.reportDetailed(this, "Unable to create packet timeout task.", e);
}
}
}
private void checkForIncompatibility(PluginManager manager) {
// Plugin authors: Notify me to remove these
String[] incompatiblePlugins = {};
for (String plugin : incompatiblePlugins) {
if (manager.getPlugin(plugin) != null) {
// Check for versions, ect.
reporter.reportWarning(this, "Detected incompatible plugin: " + plugin);
}
}
}
@Override
public void onDisable() {
// Disable compiler
if (backgroundCompiler != null) {
backgroundCompiler.shutdownAll();
backgroundCompiler = null;
BackgroundCompiler.setInstance(null);
}
// Clean up
if (asyncPacketTask >= 0) {
getServer().getScheduler().cancelTask(asyncPacketTask);
asyncPacketTask = -1;
}
unhookTask.close();
protocolManager.close();
protocolManager = null;
statistisc = null;
// Leaky ClassLoader begone!
CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter);
cleanup.resetAll();
}
/**
* Retrieves the packet protocol manager.
* @return Packet protocol manager, or NULL if it has been disabled.
*/
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.
* @return Metrics instance container.
*/
public Statistics getStatistics() {
return statistisc;
}
// 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;
}
}
@@ -1,131 +0,0 @@
/*
* 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.List;
import java.util.Set;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
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.google.common.collect.ImmutableSet;
/**
* Represents an API for accessing the Minecraft protocol.
* @author Kristian
*/
public interface ProtocolManager extends PacketStream {
/**
* Retrieves a list of every registered packet listener.
* @return Every registered packet listener.
*/
public ImmutableSet<PacketListener> getPacketListeners();
/**
* 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
* can register it again.
* @param listener - new packet listener.
*/
public void addPacketListener(PacketListener 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.
*/
public void removePacketListener(PacketListener listener);
/**
* Removes every listener associated with the given plugin.
* @param plugin - the plugin to unload.
*/
public void removePacketListeners(Plugin plugin);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* @param id - packet ID.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(int id);
/**
* 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 id - packet ID.
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(int id, boolean forceDefaults);
/**
* Construct a packet using the special builtin Minecraft constructors.
* @param id - the packet ID.
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
public PacketConstructor createPacketConstructor(int id, Object... arguments);
/**
* Completely refresh all clients about an entity.
* <p>
* 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;
/**
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
* @return Every filtered server packet.
*/
public Set<Integer> getSendingFilters();
/**
* Retrieves a immutable set containing the ID of the recieved client packets that will be observed by listeners.
* @return Every filtered client packet.
*/
public Set<Integer> getReceivingFilters();
/**
* Determines whether or not this protocol mananger has been disabled.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClosed();
/**
* Retrieve the current asyncronous packet manager.
* @return Asyncronous packet manager.
*/
public AsynchronousManager getAsynchronousManager();
}
@@ -1,344 +0,0 @@
/*
* 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.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketStream;
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.google.common.base.Objects;
/**
* Represents a filter manager for asynchronous packets.
*
* @author Kristian
*/
public class AsyncFilterManager implements AsynchronousManager {
private PacketProcessingQueue serverProcessingQueue;
private PacketSendingQueue serverQueue;
private PacketProcessingQueue clientProcessingQueue;
private PacketSendingQueue clientQueue;
private ErrorReporter reporter;
// The likely main thread
private Thread mainThread;
// Default scheduler
private BukkitScheduler scheduler;
// 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(ErrorReporter reporter, 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);
this.scheduler = scheduler;
this.manager = manager;
this.reporter = reporter;
this.mainThread = Thread.currentThread();
}
@Override
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
return registerAsyncHandler(listener, true);
}
/**
* Registers an asynchronous packet handler.
* <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 autoInject - whether or not to automatically create the corresponding synchronous listener,
* @return An asynchrouns handler.
*/
public AsyncListenerHandler registerAsyncHandler(PacketListener listener, boolean autoInject) {
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist();
// 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);
}
// We need a synchronized listener to get the ball rolling
if (autoInject) {
handler.setNullPacketListener(new NullPacketListener(listener));
manager.addPacketListener(handler.getNullPacketListener());
}
return handler;
}
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
return whitelist != null && whitelist.getWhitelist().size() > 0;
}
@Override
public void unregisterAsyncHandler(AsyncListenerHandler handler) {
if (handler == null)
throw new IllegalArgumentException("listenerToken cannot be NULL");
handler.cancel();
}
// Called by AsyncListenerHandler
void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) {
PacketListener listener = handler.getAsyncListener();
boolean synchronusOK = onMainThread();
// Unregister null packet listeners
if (handler.getNullPacketListener() != null) {
manager.removePacketListener(handler.getNullPacketListener());
}
// Just remove it from the queue(s)
if (hasValidWhitelist(listener.getSendingWhitelist())) {
List<Integer> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
// We're already taking care of this, so don't do anything
if (!cleaningUp)
serverQueue.signalPacketUpdate(removed, synchronusOK);
}
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
List<Integer> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
if (!cleaningUp)
clientQueue.signalPacketUpdate(removed, synchronusOK);
}
}
/**
* Determine if we're running on the main thread.
* @return TRUE if we are, FALSE otherwise.
*/
private boolean onMainThread() {
return Thread.currentThread().getId() == mainThread.getId();
}
@Override
public void unregisterAsyncHandlers(Plugin plugin) {
unregisterAsyncHandlers(serverProcessingQueue, plugin);
unregisterAsyncHandlers(clientProcessingQueue, plugin);
}
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
// Iterate through every packet listener
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
// Remove the listener
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
unregisterAsyncHandler(listener.getListener());
}
}
}
/**
* 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);
}
@Override
public Set<Integer> getSendingFilters() {
return serverProcessingQueue.keySet();
}
@Override
public Set<Integer> getReceivingFilters() {
return clientProcessingQueue.keySet();
}
/**
* Used to create a default asynchronous task.
* @param plugin - the calling plugin.
* @param runnable - the runnable.
*/
public void scheduleAsyncTask(Plugin plugin, Runnable runnable) {
scheduler.scheduleAsyncDelayedTask(plugin, runnable);
}
@Override
public boolean hasAsynchronousListeners(PacketEvent packet) {
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketID());
return list != null && list.size() > 0;
}
/**
* Construct a asynchronous marker with all the default values.
* @return Asynchronous marker.
*/
public AsyncMarker createAsyncMarker() {
return createAsyncMarker(AsyncMarker.DEFAULT_SENDING_DELTA, 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());
}
// Helper method
private AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta, long sendingIndex, long currentTime) {
return new AsyncMarker(manager, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta);
}
@Override
public PacketStream getPacketStream() {
return manager;
}
@Override
public ErrorReporter getErrorReporter() {
return reporter;
}
@Override
public void cleanupAll() {
cleaningUp = true;
serverProcessingQueue.cleanupAll();
serverQueue.cleanupAll();
}
@Override
public void signalPacketTransmission(PacketEvent packet) {
signalPacketTransmission(packet, onMainThread());
}
/**
* Signal that a packet is ready to be transmitted.
* @param packet - packet to signal.
* @param onMainThread - whether or not this method was run by the main thread.
*/
private void signalPacketTransmission(PacketEvent packet, boolean onMainThread) {
AsyncMarker marker = packet.getAsyncMarker();
if (marker == null)
throw new IllegalArgumentException(
"A sync packet cannot be transmitted by the asynchronous manager.");
if (!marker.isQueued())
throw new IllegalArgumentException(
"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);
}
}
/**
* Retrieve the sending queue this packet belongs to.
* @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;
}
/**
* Signal that a packet has finished processing.
* @param packet - packet to signal.
*/
public void signalFreeProcessingSlot(PacketEvent packet) {
getProcessingQueue(packet).signalProcessingDone();
}
/**
* Retrieve the processing queue this packet belongs to.
* @param packet - the packet.
* @return The server or client sending processing the packet belongs to.
*/
private PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
}
/**
* Send any due packets, or clean up packets that have expired.
*/
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);
}
}
@@ -1,268 +0,0 @@
package com.comphenix.protocol.error;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
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.events.PacketAdapter;
import com.google.common.primitives.Primitives;
/**
* Internal class used to handle exceptions.
*
* @author Kristian
*/
public class DetailedErrorReporter implements ErrorReporter {
public static final String SECOND_LEVEL_PREFIX = " ";
public static final String DEFAULT_PREFIX = " ";
public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/";
public static final String PLUGIN_NAME = "ProtocolLib";
// We don't want to spam the server
public static final int DEFAULT_MAX_ERROR_COUNT = 20;
protected String prefix;
protected String supportURL;
protected int errorCount;
protected int maxErrorCount;
protected Logger logger;
// Whether or not Apache Commons is not present
protected boolean apacheCommonsMissing;
// Map of global objects
protected Map<String, Object> globalParameters = new HashMap<String, Object>();
/**
* Create a default error reporting system.
*/
public DetailedErrorReporter() {
this(DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
}
/**
* Create a central error reporting system.
* @param prefix - default line prefix.
* @param supportURL - URL to report the error.
*/
public DetailedErrorReporter(String prefix, String supportURL) {
this(prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
}
// Attempt to get the logger.
private static Logger getBukkitLogger() {
try {
return Bukkit.getLogger();
} catch (Throwable e) {
return Logger.getLogger("Minecraft");
}
}
/**
* Create a central error reporting system.
* @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(String prefix, String supportURL, int maxErrorCount, Logger logger) {
this.prefix = prefix;
this.supportURL = supportURL;
this.maxErrorCount = maxErrorCount;
this.logger = logger;
}
@Override
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " + methodName + " for " +
PacketAdapter.getPluginName(sender), error);
}
@Override
public void reportWarning(Object sender, String message) {
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message);
}
@Override
public void reportWarning(Object sender, String message, Throwable error) {
logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message, error);
}
private String getSenderName(Object sender) {
if (sender != null)
return sender.getClass().getSimpleName();
else
return "NULL";
}
@Override
public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) {
// Do not overtly spam the server!
if (++errorCount > maxErrorCount) {
String maxReached = String.format("Reached maxmimum error count. Cannot pass error %s from %s.", error, sender);
logger.severe(maxReached);
return;
}
StringWriter text = new StringWriter();
PrintWriter writer = new PrintWriter(text);
// Helpful message
writer.println("[ProtocolLib] INTERNAL ERROR: " + message);
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 (error != null)
error.printStackTrace(writer);
// Data dump!
writer.println(" ===== DUMP =====");
// Relevant parameters
if (parameters != null && parameters.length > 0) {
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));
}
}
// 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));
// Add the server version too
if (Bukkit.getServer() != null) {
writer.println("Server:");
writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
}
// Make sure it is reported
logger.severe(addPrefix(text.toString(), 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);
}
protected String getStringDescription(Object value) {
// We can't only rely on toString.
if (value == null) {
return "[NULL]";
} if (isSimpleType(value)) {
return value.toString();
} else {
try {
if (!apacheCommonsMissing)
return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null));
} catch (Throwable ex) {
// Apache is probably missing
logger.warning("Cannot find Apache Commons. Object introspection disabled.");
apacheCommonsMissing = true;
}
// Just use toString()
return String.format("%s", value);
}
}
/**
* 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 boolean isSimpleType(Object test) {
return test instanceof String || Primitives.isWrapperType(test.getClass());
}
public int getErrorCount() {
return errorCount;
}
public void setErrorCount(int errorCount) {
this.errorCount = errorCount;
}
public int getMaxErrorCount() {
return maxErrorCount;
}
public void setMaxErrorCount(int maxErrorCount) {
this.maxErrorCount = maxErrorCount;
}
/**
* Adds the given global parameter. It will be included in every error report.
* @param key - name of parameter.
* @param value - the global parameter itself.
*/
public void addGlobalParameter(String key, Object value) {
globalParameters.put(key, value);
}
public Object getGlobalParameter(String key) {
return globalParameters.get(key);
}
public void clearGlobalParameters() {
globalParameters.clear();
}
public Set<String> globalParameters() {
return globalParameters.keySet();
}
public String getSupportURL() {
return supportURL;
}
public void setSupportURL(String supportURL) {
this.supportURL = supportURL;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
}
@@ -1,39 +0,0 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
public interface ErrorReporter {
/**
* Prints a small minimal error report about 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 warning message from the current plugin.
* @param sender - the object containing the caller method.
* @param message - error message.
*/
public abstract void reportWarning(Object sender, String message);
/**
* Prints a warning message from the current plugin.
* @param sender - the object containing the caller method.
* @param message - error message.
* @param error - the exception that was thrown.
*/
public abstract void reportWarning(Object sender, String message, Throwable error);
/**
* Prints a detailed error report about an unhandled exception.
* @param sender - the object containing the caller method.
* @param message - an error message to include.
* @param error - the exception that was thrown in the caller method.
* @param parameters - parameters from the caller method.
*/
public abstract void reportDetailed(Object sender, String message, Throwable error, Object... parameters);
}
@@ -1,161 +0,0 @@
/*
* 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.Set;
import com.comphenix.protocol.injector.GamePhase;
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
*/
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);
}
/**
* 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.
* @param gamePhase - which game phase to receieve notifications on.
*/
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist, GamePhase gamePhase) {
this.priority = priority;
this.whitelist = whitelist;
this.gamePhase = gamePhase;
}
/**
* Creates a packet whitelist of a given priority for a list of packets.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
*/
public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) {
this.priority = priority;
this.whitelist = Sets.newHashSet(whitelist);
this.gamePhase = GamePhase.PLAYING;
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
*/
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase) {
this.priority = priority;
this.whitelist = Sets.newHashSet(whitelist);
this.gamePhase = gamePhase;
}
/**
* 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;
}
/**
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
* @return Execution order in terms of priority.
*/
public ListenerPriority getPriority() {
return priority;
}
/**
* Retrieves the list of packets that will be observed by the listeners.
* @return Packet whitelist.
*/
public Set<Integer> getWhitelist() {
return whitelist;
}
/**
* Retrieve which game phase this listener is active under.
* @return The active game phase.
*/
public GamePhase getGamePhase() {
return gamePhase;
}
@Override
public int hashCode(){
return Objects.hashCode(priority, whitelist, gamePhase);
}
/**
* 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.
* @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) {
if (whitelist != null) {
for (int i = 0; i < idList.length; i++) {
if (whitelist.getWhitelist().contains(idList[i]))
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;
}
}
@Override
public String toString() {
if (this == EMPTY_WHITELIST)
return "EMPTY_WHITELIST";
else
return Objects.toStringHelper(this)
.add("priority", priority)
.add("packets", whitelist).toString();
}
}
@@ -1,205 +0,0 @@
/*
* 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.Set;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.GamePhase;
/**
* Represents a packet listener with useful constructors.
* <p>
* Remember to override onPacketReceiving() and onPacketSending(), depending on the ConnectionSide.
* @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 listener with default priority.
* @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.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, packets);
}
/**
* Initialize a packet listener for a single connection side.
* @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.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Set<Integer> packets) {
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets.toArray(new Integer[0]));
}
/**
* 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.
* @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.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Set<Integer> packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets.toArray(new Integer[0]));
}
/**
* 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.
* @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.
*/
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.
* @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.
*/
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.
* @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.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, GamePhase.PLAYING, 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.
* @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.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Integer... packets) {
if (plugin == null)
throw new IllegalArgumentException("plugin cannot be null");
if (connectionSide == null)
throw new IllegalArgumentException("connectionSide cannot be null");
if (listenerPriority == null)
throw new IllegalArgumentException("listenerPriority cannot be null");
if (gamePhase == null)
throw new IllegalArgumentException("gamePhase cannot be NULL");
if (packets == null)
throw new IllegalArgumentException("packets cannot be null");
// Add whitelists
if (connectionSide.isForServer())
sendingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
if (connectionSide.isForClient())
receivingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
this.plugin = plugin;
this.connectionSide = connectionSide;
}
@Override
public void onPacketReceiving(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
}
@Override
public void onPacketSending(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
/**
* 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) {
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) {
// Try to get the plugin name
try {
if (plugin == null)
return "UNKNOWN";
else
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),
sendingWhitelist,
receivingWhitelist);
}
}
@@ -1,413 +0,0 @@
/*
* 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.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.injector.StructureCache;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import net.minecraft.server.Packet;
/**
* Represents a Minecraft packet indirectly.
*
* @author Kristian
*/
public class PacketContainer implements Serializable {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 2074805748222377230L;
protected int id;
protected transient Packet handle;
// Current structure modifier
protected transient StructureModifier<Object> structureModifier;
// Check whether or not certain classes exists
private static boolean hasWorldType = false;
// The getEntity method
private static Method getEntity;
// Support for serialization
private static Method writeMethod;
private static Method readMethod;
static {
try {
Class.forName("net.minecraft.server.WorldType");
hasWorldType = true;
} catch (ClassNotFoundException e) {
}
}
/**
* Creates a packet container for a new packet.
* @param id - ID of the packet to create.
*/
public PacketContainer(int id) {
this(id, StructureCache.newPacket(id));
}
/**
* Creates a packet container for an existing packet.
* @param id - ID of the given packet.
* @param handle - contained packet.
*/
public PacketContainer(int id, Packet handle) {
this(id, handle, StructureCache.getStructure(id).withTarget(handle));
}
/**
* Creates a packet container for an existing packet.
* @param id - ID of the given packet.
* @param handle - contained packet.
* @param structure - structure modifier.
*/
public PacketContainer(int id, Packet handle, StructureModifier<Object> structure) {
if (handle == null)
throw new IllegalArgumentException("handle cannot be null.");
this.id = id;
this.handle = handle;
this.structureModifier = structure;
}
/**
* Retrieves the underlying Minecraft packet.
* @return Underlying Minecraft packet.
*/
public Packet getHandle() {
return handle;
}
/**
* Retrieves the generic structure modifier for this packet.
* @return Structure modifier.
*/
public StructureModifier<Object> getModifier() {
return structureModifier;
}
/**
* Retrieves a read/write structure for every field with the given type.
* @param primitiveType - the type to find.
* @return A modifier for this specific type.
*/
public <T> StructureModifier<T> getSpecificModifier(Class<T> primitiveType) {
return structureModifier.withType(primitiveType);
}
/**
* Retrieves a read/write structure for ItemStack.
* <p>
* This modifier will automatically marshall between the Bukkit ItemStack and the
* internal Minecraft ItemStack.
* @return A modifier for ItemStack fields.
*/
public StructureModifier<ItemStack> getItemModifier() {
// Convert to and from the Bukkit wrapper
return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class,
getIgnoreNull(new EquivalentConverter<ItemStack>() {
public Object getGeneric(ItemStack specific) {
return toStackNMS(specific);
}
@Override
public ItemStack getSpecific(Object generic) {
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
}
@Override
public Class<ItemStack> getSpecificType() {
return ItemStack.class;
}
}));
}
/**
* Retrieves a read/write structure for arrays of ItemStacks.
* <p>
* This modifier will automatically marshall between the Bukkit ItemStack and the
* internal Minecraft ItemStack.
* @return A modifier for ItemStack array fields.
*/
public StructureModifier<ItemStack[]> getItemArrayModifier() {
// Convert to and from the Bukkit wrapper
return structureModifier.<ItemStack[]>withType(
net.minecraft.server.ItemStack[].class,
getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
public Object getGeneric(ItemStack[] specific) {
net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length];
// Unwrap every item
for (int i = 0; i < result.length; i++) {
result[i] = toStackNMS(specific[i]);
}
return result;
}
@Override
public ItemStack[] getSpecific(Object generic) {
net.minecraft.server.ItemStack[] input = (net.minecraft.server.ItemStack[]) generic;
ItemStack[] result = new ItemStack[input.length];
// Add the wrapper
for (int i = 0; i < result.length; i++) {
result[i] = new CraftItemStack(input[i]);
}
return result;
}
@Override
public Class<ItemStack[]> getSpecificType() {
return ItemStack[].class;
}
}));
}
/**
* Convert an item stack to the NMS equivalent.
* @param stack - Bukkit stack to convert.
* @return A bukkit stack.
*/
private net.minecraft.server.ItemStack toStackNMS(ItemStack stack) {
// We must be prepared for an object that simply implements ItemStcak
if (stack instanceof CraftItemStack) {
return ((CraftItemStack) stack).getHandle();
} else {
return (new CraftItemStack(stack)).getHandle();
}
}
/**
* Retrieves a read/write structure for the world type enum.
* <p>
* This modifier will automatically marshall between the Bukkit world type and the
* internal Minecraft world type.
* @return A modifier for world type fields.
*/
public StructureModifier<WorldType> getWorldTypeModifier() {
if (!hasWorldType) {
// We couldn't find the Minecraft equivalent
return structureModifier.withType(null);
}
// Convert to and from the Bukkit wrapper
return structureModifier.<WorldType>withType(
net.minecraft.server.WorldType.class,
getIgnoreNull(new EquivalentConverter<WorldType>() {
@Override
public Object getGeneric(WorldType specific) {
return net.minecraft.server.WorldType.getType(specific.getName());
}
@Override
public WorldType getSpecific(Object generic) {
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
return WorldType.getByName(type.name());
}
@Override
public Class<WorldType> getSpecificType() {
return WorldType.class;
}
}));
}
/**
* Retrieves a read/write structure for entity objects.
* <p>
* Note that entities are transmitted by integer ID, and the type may not be enough
* to distinguish between entities and other values. Thus, this structure modifier
* MAY return null or invalid entities for certain fields. Using the correct index
* is essential.
*
* @return A modifier entity types.
*/
public StructureModifier<Entity> getEntityModifier(World world) {
final Object worldServer = ((CraftWorld) world).getHandle();
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
final World worldCopy = world;
if (getEntity == null)
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
"getEntity", nmsEntityClass, new Class[] { int.class });
// Convert to and from the Bukkit wrapper
return structureModifier.<Entity>withType(
int.class,
getIgnoreNull(new EquivalentConverter<Entity>() {
@Override
public Object getGeneric(Entity specific) {
// Simple enough
return specific.getEntityId();
}
@Override
public Entity getSpecific(Object generic) {
try {
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
getEntity.invoke(worldServer, generic);
Integer id = (Integer) generic;
// Attempt to get the Bukkit entity
if (nmsEntity != null) {
return nmsEntity.getBukkitEntity();
} else {
// Maybe it's a player that's just logged in? Try a search
for (Player player : worldCopy.getPlayers()) {
if (player.getEntityId() == id) {
return player;
}
}
return null;
}
} catch (IllegalArgumentException e) {
throw new RuntimeException("Incorrect arguments detected.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot read field due to a security limitation.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
}
}
@Override
public Class<Entity> getSpecificType() {
return Entity.class;
}
}));
}
private <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
// Automatically wrap all parameters to the delegate with a NULL check
return new EquivalentConverter<TType>() {
public Object getGeneric(TType specific) {
if (specific != null)
return delegate.getGeneric(specific);
else
return null;
}
@Override
public TType getSpecific(Object generic) {
if (generic != null)
return delegate.getSpecific(generic);
else
return null;
}
@Override
public Class<TType> getSpecificType() {
return delegate.getSpecificType();
}
};
}
/**
* Retrieves the ID of this packet.
* @return Packet ID.
*/
public int getID() {
return id;
}
private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization
output.defaultWriteObject();
// We'll take care of NULL packets as well
output.writeBoolean(handle != null);
// Retrieve the write method by reflection
if (writeMethod == null)
writeMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("write", DataOutputStream.class);
try {
// Call the write-method
writeMethod.invoke(handle, new DataOutputStream(output));
} catch (IllegalArgumentException e) {
throw new IOException("Minecraft packet doesn't support DataOutputStream", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Insufficient security privileges.", e);
} catch (InvocationTargetException e) {
throw new IOException("Could not serialize Minecraft packet.", e);
}
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
input.defaultReadObject();
// Get structure modifier
structureModifier = StructureCache.getStructure(id);
// Don't read NULL packets
if (input.readBoolean()) {
// Create a default instance of the packet
handle = StructureCache.newPacket(id);
// Retrieve the read method by reflection
if (readMethod == null)
readMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("read", DataInputStream.class);
// Call the read method
try {
readMethod.invoke(handle, new DataInputStream(input));
} catch (IllegalArgumentException e) {
throw new IOException("Minecraft packet doesn't support DataInputStream", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Insufficient security privileges.", e);
} catch (InvocationTargetException e) {
throw new IOException("Could not deserialize Minecraft packet.", e);
}
// And we're done
structureModifier = structureModifier.withTarget(handle);
}
}
}
@@ -1,214 +0,0 @@
/*
* 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.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.EventObject;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import com.comphenix.protocol.async.AsyncMarker;
public class PacketEvent extends EventObject implements Cancellable {
/**
* Automatically generated by Eclipse.
*/
private static final long serialVersionUID = -5360289379097430620L;
private transient Player player;
private PacketContainer packet;
private boolean serverPacket;
private boolean cancel;
private AsyncMarker asyncMarker;
private boolean asynchronous;
/**
* Use the static constructors to create instances of this event.
* @param source - the event source.
*/
public PacketEvent(Object source) {
super(source);
}
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
super(source);
this.packet = packet;
this.player = player;
this.serverPacket = serverPacket;
}
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
super(origial.source);
this.packet = origial.packet;
this.player = origial.player;
this.cancel = origial.cancel;
this.serverPacket = origial.serverPacket;
this.asyncMarker = asyncMarker;
this.asynchronous = true;
}
/**
* Creates an event representing a client packet transmission.
* @param source - the event source.
* @param packet - the packet.
* @param client - the client that sent the packet.
* @return The event.
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
return new PacketEvent(source, packet, client, false);
}
/**
* Creates an event representing a server packet transmission.
* @param source - the event source.
* @param packet - the packet.
* @param recipient - the client that will receieve the packet.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
return new PacketEvent(source, packet, recipient, true);
}
/**
* Create an asynchronous packet event from a synchronous event and a async marker.
* @param event - the original synchronous event.
* @param marker - the asynchronous marker.
* @return The new packet event.
*/
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
return new PacketEvent(event, marker);
}
/**
* Retrieves the packet that will be sent to the player.
* @return Packet to send to the player.
*/
public PacketContainer getPacket() {
return packet;
}
/**
* Replace the packet that will be sent to the player.
* @param packet - the packet that will be sent instead.
*/
public void setPacket(PacketContainer packet) {
this.packet = packet;
}
/**
* Retrieves the packet ID.
* @return The current packet ID.
*/
public int getPacketID() {
return packet.getID();
}
/**
* Retrieves whether or not the packet should be cancelled.
* @return TRUE if it should be cancelled, FALSE otherwise.
*/
public boolean isCancelled() {
return cancel;
}
/**
* Sets whether or not the packet should be cancelled.
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
*/
public void setCancelled(boolean cancel) {
this.cancel = cancel;
}
/**
* Retrieves the player that has sent the packet or is recieving it.
* @return The player associated with this event.
*/
public Player getPlayer() {
return player;
}
/**
* Whether or not this packet was created by the server.
* <p>
* Most listeners can deduce this by noting which listener method was invoked.
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
*/
public boolean isServerPacket() {
return serverPacket;
}
/**
* Retrieve the asynchronous marker.
* <p>
* If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
* <p>
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
* @return The current asynchronous marker, or NULL.
*/
public AsyncMarker getAsyncMarker() {
return asyncMarker;
}
/**
* 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.
* @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");
this.asyncMarker = asyncMarker;
}
/**
* Determine if the packet event has been executed asynchronously or not.
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
*/
public boolean isAsynchronous() {
return asynchronous;
}
private void writeObject(ObjectOutputStream output) throws IOException {
// 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);
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
input.defaultReadObject();
final SerializedOfflinePlayer offlinePlayer = (SerializedOfflinePlayer) input.readObject();
if (offlinePlayer != null) {
// Better than nothing
player = offlinePlayer.getPlayer();
}
}
}
@@ -1,115 +0,0 @@
/*
* 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 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.injector.PacketConstructor.Unwrapper;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
/**
* 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>
* </ul>
*
* @author Kristian
*/
public class BukkitUnwrapper implements Unwrapper {
private static Map<Class<?>, Method> cache = new ConcurrentHashMap<Class<?>, Method>();
@SuppressWarnings("unchecked")
@Override
public Object unwrapItem(Object wrappedObject) {
// Special cases
if (wrappedObject == null) {
return null;
} else if (wrappedObject instanceof Collection) {
return handleCollection((Collection<Object>) wrappedObject);
}
Class<?> currentClass = wrappedObject.getClass();
Method cachedMethod = initializeCache(currentClass);
try {
// Retrieve the handle
if (cachedMethod != null)
return cachedMethod.invoke(wrappedObject);
else
return null;
} catch (IllegalArgumentException e) {
// Impossible
return null;
} catch (IllegalAccessException e) {
return null;
} catch (InvocationTargetException e) {
// This is REALLY bad
throw new RuntimeException("Minecraft error.", e);
}
}
private Object handleCollection(Collection<Object> wrappedObject) {
@SuppressWarnings("unchecked")
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
if (copy != null) {
// Unwrap every element
for (Object element : wrappedObject) {
copy.add(unwrapItem(element));
}
return copy;
} else {
// Impossible
return null;
}
}
private Method initializeCache(Class<?> type) {
// See if we're already determined this
if (cache.containsKey(type)) {
// We will never remove from the cache, so this ought to be thread safe
return cache.get(type);
}
try {
Method find = type.getMethod("getHandle");
// It's thread safe, as getMethod should return the same handle
cache.put(type, find);
return find;
} catch (SecurityException e) {
return null;
} catch (NoSuchMethodException e) {
return null;
}
}
}
@@ -1,206 +0,0 @@
/*
* 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 java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.collect.Lists;
/**
* Used to perform certain operations on entities.
*
* @author Kristian
*/
class EntityUtilities {
private static Field entityTrackerField;
private static Field trackedEntitiesField;
private static Field trackedPlayersField;
private static Method hashGetMethod;
private static Method scanPlayersMethod;
/*
* While this function may look pretty bad, it's essentially just a reflection-warped
* version of the following:
*
* @SuppressWarnings("unchecked")
* public static void updateEntity2(Entity entity, List<Player> observers) {
*
* World world = entity.getWorld();
* WorldServer worldServer = ((CraftWorld) world).getHandle();
*
* EntityTracker tracker = worldServer.tracker;
* EntityTrackerEntry entry = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
*
* List<EntityPlayer> nmsPlayers = getNmsPlayers(observers);
*
* entry.trackedPlayers.removeAll(nmsPlayers);
* entry.scanPlayers(nmsPlayers);
* }
*
* private static List<EntityPlayer> getNmsPlayers(List<Player> players) {
* List<EntityPlayer> nsmPlayers = new ArrayList<EntityPlayer>();
*
* for (Player bukkitPlayer : players) {
* CraftPlayer craftPlayer = (CraftPlayer) bukkitPlayer;
* nsmPlayers.add(craftPlayer.getHandle());
* }
*
* return nsmPlayers;
* }
*
*/
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
World world = entity.getWorld();
Object worldServer = ((CraftWorld) world).getHandle();
// We have to rely on the class naming here.
if (entityTrackerField == null)
entityTrackerField = FuzzyReflection.fromObject(worldServer).getFieldByType(".*Tracker");
// Get the tracker
Object tracker = null;
try {
tracker = FieldUtils.readField(entityTrackerField, worldServer, false);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'tracker' field due to security limitations.", e);
}
if (trackedEntitiesField == null) {
@SuppressWarnings("rawtypes")
Set<Class> ignoredTypes = new HashSet<Class>();
// Well, this is more difficult. But we're looking for a Minecraft object that is not
// created by the constructor(s).
for (Constructor<?> constructor : tracker.getClass().getConstructors()) {
for (Class<?> type : constructor.getParameterTypes()) {
ignoredTypes.add(type);
}
}
// The Minecraft field that's NOT filled in by the constructor
trackedEntitiesField = FuzzyReflection.fromObject(tracker, true).
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT, ignoredTypes);
}
// Read the entity hashmap
Object trackedEntities = null;
try {
trackedEntities = FieldUtils.readField(trackedEntitiesField, tracker, true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'trackedEntities' field due to security limitations.", e);
}
// Getting the "get" method is pretty hard, but first - try to just get it by name
if (hashGetMethod == null) {
Class<?> type = trackedEntities.getClass();
try {
hashGetMethod = type.getMethod("get", int.class);
} catch (NoSuchMethodException e) {
Class<?>[] params = { int.class };
// Then it's probably the lowest named method that takes an int-parameter
for (Method method : type.getMethods()) {
if (Arrays.equals(params, method.getParameterTypes())) {
if (hashGetMethod == null ||
method.getName().compareTo(hashGetMethod.getName()) < 0) {
hashGetMethod = method;
}
}
}
}
}
try {
//EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
Object trackerEntry = hashGetMethod.invoke(trackedEntities, entity.getEntityId());
if (trackedPlayersField == null) {
// This one is fairly easy
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
}
// Phew, finally there.
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
List<Object> nmsPlayers = unwrapBukkit(observers);
// trackEntity.trackedPlayers.clear();
trackedPlayers.removeAll(nmsPlayers);
// We have to rely on a NAME once again. Damn it.
if (scanPlayersMethod == null) {
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
}
//trackEntity.scanPlayers(server.players);
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
} catch (IllegalArgumentException e) {
throw e;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occurred in Minecraft.", e);
} catch (SecurityException e) {
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
} catch (NoSuchMethodException e) {
throw new FieldAccessException("Canot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
}
}
private static List<Object> unwrapBukkit(List<Player> players) {
List<Object> output = Lists.newArrayList();
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
// Get the NMS equivalent
for (Player player : players) {
Object result = unwrapper.unwrapItem(player);
if (result != null)
output.add(result);
else
throw new IllegalArgumentException("Cannot unwrap item " + player);
}
return output;
}
}
@@ -1,39 +0,0 @@
package com.comphenix.protocol.injector;
/**
* The current player phase. This is used to limit the number of different injections.
*
* @author Kristian
*/
public enum GamePhase {
/**
* Only listen for packets sent or received before a player has logged in.
*/
LOGIN,
/**
* Only listen for packets sent or received after a player has logged in.
*/
PLAYING,
/**
* Listen for every sent and received packet.
*/
BOTH;
/**
* Determine if the current value represents the login phase.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasLogin() {
return this == LOGIN || this == BOTH;
}
/**
* Determine if the current value represents the playing phase.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasPlaying() {
return this == PLAYING || this == BOTH;
}
}
@@ -1,183 +0,0 @@
/*
* 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 java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraft.server.Packet;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
/**
* Static registries in Minecraft.
*
* @author Kristian
*/
@SuppressWarnings("rawtypes")
class MinecraftRegistry {
// Fuzzy reflection
private static FuzzyReflection packetRegistry;
// The packet class to packet ID translator
private static Map<Class, Integer> packetToID;
// Whether or not certain packets are sent by the client or the server
private static Set<Integer> serverPackets;
private static Set<Integer> clientPackets;
// New proxy values
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
// Vanilla packets
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
@SuppressWarnings({ "unchecked" })
public static Map<Class, Integer> getPacketToID() {
// Initialize it, if we haven't already
if (packetToID == null) {
try {
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
}
}
return packetToID;
}
/**
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
* @return Reflected packet registry.
*/
private static FuzzyReflection getPacketRegistry() {
if (packetRegistry == null)
packetRegistry = FuzzyReflection.fromClass(Packet.class, true);
return packetRegistry;
}
/**
* Retrieve the injected proxy classes handlig each packet ID.
* @return Injected classes.
*/
public static Map<Integer, Class> getOverwrittenPackets() {
return overwrittenPackets;
}
/**
* Retrieve the vanilla classes handling each packet ID.
* @return Vanilla classes.
*/
public static Map<Integer, Class> getPreviousPackets() {
return previousValues;
}
/**
* Retrieve every known and supported server packet.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
public static Set<Integer> getServerPackets() throws FieldAccessException {
initializeSets();
return serverPackets;
}
/**
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public static Set<Integer> getClientPackets() throws FieldAccessException {
initializeSets();
return clientPackets;
}
@SuppressWarnings("unchecked")
private static void initializeSets() throws FieldAccessException {
if (serverPackets == null || clientPackets == null) {
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
try {
if (sets.size() > 1) {
serverPackets = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPackets = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
// Impossible
if (serverPackets == null || clientPackets == null)
throw new FieldAccessException("Packet sets are in an illegal state.");
// NEVER allow callers to modify the underlying sets
serverPackets = ImmutableSet.copyOf(serverPackets);
clientPackets = ImmutableSet.copyOf(clientPackets);
} else {
throw new FieldAccessException("Cannot retrieve packet client/server sets.");
}
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access field.", e);
}
}
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID) {
return getPacketClassFromID(packetID, false);
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
// Optimized lookup
if (lookup.containsKey(packetID)) {
return lookup.get(packetID);
}
// Will most likely not be used
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
if (Objects.equal(entry.getValue(), packetID)) {
// Attempt to get the vanilla class here too
if (!forceVanilla || entry.getKey().getName().startsWith("net.minecraft.server"))
return entry.getKey();
}
}
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
}
}
@@ -1,824 +0,0 @@
/*
* 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 java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.Server;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.*;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
public final class PacketFilterManager implements ProtocolManager, ListenerInvoker {
/**
* Sets the inject hook type. Different types allow for maximum compatibility.
* @author Kristian
*/
public enum PlayerInjectHooks {
/**
* The injection hook that does nothing. Set when every other inject hook fails.
*/
NONE,
/**
* Override the network handler object itself. Only works in 1.3.
* <p>
* Cannot intercept MapChunk packets.
*/
NETWORK_MANAGER_OBJECT,
/**
* Override the packet queue lists in NetworkHandler.
* <p>
* Cannot intercept MapChunk packets.
*/
NETWORK_HANDLER_FIELDS,
/**
* Override the server handler object. Versatile, but a tad slower.
*/
NETWORK_SERVER_OBJECT;
}
// The amount of time to wait until we actually unhook every player
private static final int TICKS_PER_SECOND = 20;
private static final int UNHOOK_DELAY = 5 * TICKS_PER_SECOND;
// Delayed unhook
private DelayedSingleTask unhookTask;
// Create a concurrent set
private Set<PacketListener> packetListeners =
Collections.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
// Packet injection
private PacketInjector packetInjector;
// Different injection types per game phase
private PlayerInjectionHandler playerInjection;
// The two listener containers
private SortedPacketListenerList recievedListeners = new SortedPacketListenerList();
private SortedPacketListenerList sendingListeners = new SortedPacketListenerList();
// Whether or not this class has been closed
private volatile boolean hasClosed;
// The default class loader
private ClassLoader classLoader;
// Error repoter
private ErrorReporter reporter;
// The current server
private Server server;
// The async packet handler
private AsyncFilterManager asyncFilterManager;
// Valid server and client packets
private Set<Integer> serverPackets;
private Set<Integer> clientPackets;
// Ensure that we're not performing too may injections
private AtomicInteger phaseLoginCount = new AtomicInteger(0);
private AtomicInteger phasePlayingCount = new AtomicInteger(0);
// Whether or not plugins are using the send/receive methods
private AtomicBoolean packetCreation = new AtomicBoolean();
/**
* Only create instances of this class if protocol lib is disabled.
* @param unhookTask
*/
public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, ErrorReporter reporter) {
if (reporter == null)
throw new IllegalArgumentException("reporter cannot be NULL.");
if (classLoader == null)
throw new IllegalArgumentException("classLoader cannot be NULL.");
// Just boilerplate
final DelayedSingleTask finalUnhookTask = unhookTask;
// References
this.unhookTask = unhookTask;
this.server = server;
this.classLoader = classLoader;
this.reporter = reporter;
// Used to determine if injection is needed
Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
@Override
public boolean apply(@Nullable GamePhase phase) {
boolean result = true;
if (phase.hasLogin())
result &= getPhaseLoginCount() > 0;
// Note that we will still hook players if the unhooking has been delayed
if (phase.hasPlaying())
result &= getPhasePlayingCount() > 0 || finalUnhookTask.isRunning();
return result;
}
};
try {
// Initialize injection mangers
this.playerInjection = new PlayerInjectionHandler(classLoader, reporter, isInjectionNecessary, this, packetListeners, server);
this.packetInjector = new PacketInjector(classLoader, this, playerInjection, reporter);
this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this);
// Attempt to load the list of server and client packets
try {
this.serverPackets = MinecraftRegistry.getServerPackets();
this.clientPackets = MinecraftRegistry.getClientPackets();
} catch (FieldAccessException e) {
reporter.reportWarning(this, "Cannot load server and client packet list.", e);
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to initialize packet injector.", e);
}
}
@Override
public AsynchronousManager getAsynchronousManager() {
return asyncFilterManager;
}
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
public PlayerInjectHooks getPlayerHook() {
return playerInjection.getPlayerHook();
}
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
public void setPlayerHook(PlayerInjectHooks playerHook) {
playerInjection.setPlayerHook(playerHook);
}
@Override
public ImmutableSet<PacketListener> getPacketListeners() {
return ImmutableSet.copyOf(packetListeners);
}
@Override
public void addPacketListener(PacketListener listener) {
if (listener == null)
throw new IllegalArgumentException("listener cannot be NULL.");
// A listener can only be added once
if (packetListeners.contains(listener))
return;
ListeningWhitelist sending = listener.getSendingWhitelist();
ListeningWhitelist receiving = listener.getReceivingWhitelist();
boolean hasSending = sending != null && sending.isEnabled();
boolean hasReceiving = receiving != null && receiving.isEnabled();
if (hasSending || hasReceiving) {
// Add listeners and hooks
if (hasSending) {
verifyWhitelist(listener, sending);
sendingListeners.addListener(listener, sending);
enablePacketFilters(listener, ConnectionSide.SERVER_SIDE, sending.getWhitelist());
// Make sure this is possible
playerInjection.checkListener(listener);
}
if (hasReceiving) {
verifyWhitelist(listener, receiving);
recievedListeners.addListener(listener, receiving);
enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
}
// Increment phases too
if (hasSending)
incrementPhases(sending.getGamePhase());
if (hasReceiving)
incrementPhases(receiving.getGamePhase());
// Inform our injected hooks
packetListeners.add(listener);
}
}
/**
* Invoked to handle the different game phases of a added listener.
* @param phase - listener's game game phase.
*/
private void incrementPhases(GamePhase phase) {
if (phase.hasLogin())
phaseLoginCount.incrementAndGet();
// We may have to inject into every current player
if (phase.hasPlaying()) {
if (phasePlayingCount.incrementAndGet() == 1) {
// If we're about to uninitialize every player, cancel that instead
if (unhookTask.isRunning())
unhookTask.cancel();
else
// Inject our hook into already existing players
initializePlayers(server.getOnlinePlayers());
}
}
}
/**
* Invoked to handle the different game phases of a removed listener.
* @param phase - listener's game game phase.
*/
private void decrementPhases(GamePhase phase) {
if (phase.hasLogin())
phaseLoginCount.decrementAndGet();
// We may have to inject into every current player
if (phase.hasPlaying()) {
if (phasePlayingCount.decrementAndGet() == 0) {
// Schedule unhooking in the future
unhookTask.schedule(UNHOOK_DELAY, new Runnable() {
@Override
public void run() {
// Inject our hook into already existing players
uninitializePlayers(server.getOnlinePlayers());
}
});
}
}
}
/**
* Determine if the packet IDs in a whitelist is valid.
* @param listener - the listener that will be mentioned in the error.
* @param whitelist - whitelist of packet IDs.
* @throws IllegalArgumentException If the whitelist is illegal.
*/
public static void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) {
for (Integer id : whitelist.getWhitelist()) {
if (id >= 256 || id < 0) {
throw new IllegalArgumentException(String.format("Invalid packet id %s in listener %s.",
id, PacketAdapter.getPluginName(listener))
);
}
}
}
@Override
public void removePacketListener(PacketListener listener) {
if (listener == null)
throw new IllegalArgumentException("listener cannot be NULL");
List<Integer> sendingRemoved = null;
List<Integer> receivingRemoved = null;
ListeningWhitelist sending = listener.getSendingWhitelist();
ListeningWhitelist receiving = listener.getReceivingWhitelist();
// Remove from the overal list of listeners
if (!packetListeners.remove(listener))
return;
// Remove listeners and phases
if (sending != null && sending.isEnabled()) {
sendingRemoved = sendingListeners.removeListener(listener, sending);
decrementPhases(sending.getGamePhase());
}
if (receiving != null && receiving.isEnabled()) {
receivingRemoved = recievedListeners.removeListener(listener, receiving);
decrementPhases(receiving.getGamePhase());
}
// Remove hooks, if needed
if (sendingRemoved != null && sendingRemoved.size() > 0)
disablePacketFilters(ConnectionSide.SERVER_SIDE, sendingRemoved);
if (receivingRemoved != null && receivingRemoved.size() > 0)
disablePacketFilters(ConnectionSide.CLIENT_SIDE, receivingRemoved);
}
@Override
public void removePacketListeners(Plugin plugin) {
// Iterate through every packet listener
for (PacketListener listener : packetListeners) {
// Remove the listener
if (Objects.equal(listener.getPlugin(), plugin)) {
removePacketListener(listener);
}
}
// Do the same for the asynchronous events
asyncFilterManager.unregisterAsyncHandlers(plugin);
}
@Override
public void invokePacketRecieving(PacketEvent event) {
handlePacket(recievedListeners, event, false);
}
@Override
public void invokePacketSending(PacketEvent event) {
handlePacket(sendingListeners, event, true);
}
/**
* Handle a packet sending or receiving event.
* <p>
* Note that we also handle asynchronous events.
* @param packetListeners - packet listeners that will receive this event.
* @param event - the evnet to broadcast.
*/
private void handlePacket(SortedPacketListenerList packetListeners, PacketEvent event, boolean sending) {
// By default, asynchronous packets are queued for processing
if (asyncFilterManager.hasAsynchronousListeners(event)) {
event.setAsyncMarker(asyncFilterManager.createAsyncMarker());
}
// Process synchronous events
if (sending)
packetListeners.invokePacketSending(reporter, event);
else
packetListeners.invokePacketRecieving(reporter, event);
// To cancel asynchronous processing, use the async marker
if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) {
asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker());
// The above makes a copy of the event, so it's safe to cancel it
event.setCancelled(true);
}
}
// NULL marker mean we're dealing with no asynchronous listeners
private boolean hasAsyncCancelled(AsyncMarker marker) {
return marker == null || marker.isAsyncCancelled();
}
/**
* Enables packet events for a given packet ID.
* <p>
* Note that all packets are disabled by default.
*
* @param listener - the listener that requested to enable these filters.
* @param side - which side the event will arrive from.
* @param packets - the packet id(s).
*/
private void enablePacketFilters(PacketListener listener, ConnectionSide side, Iterable<Integer> packets) {
if (side == null)
throw new IllegalArgumentException("side cannot be NULL.");
// Note the difference between unsupported and valid.
// Every packet ID between and including 0 - 255 is valid, but only a subset is supported.
for (int packetID : packets) {
// Only register server packets that are actually supported by Minecraft
if (side.isForServer()) {
if (serverPackets != null && serverPackets.contains(packetID))
playerInjection.addPacketHandler(packetID);
else
reporter.reportWarning(this, String.format(
"[%s] Unsupported server packet ID in current Minecraft version: %s",
PacketAdapter.getPluginName(listener), packetID
));
}
// As above, only for client packets
if (side.isForClient() && packetInjector != null) {
if (clientPackets != null && clientPackets.contains(packetID))
packetInjector.addPacketHandler(packetID);
else
reporter.reportWarning(this, String.format(
"[%s] Unsupported client packet ID in current Minecraft version: %s",
PacketAdapter.getPluginName(listener), packetID
));
}
}
}
/**
* Disables packet events from a given packet ID.
* @param packets - the packet id(s).
* @param side - which side the event no longer should arrive from.
*/
private void disablePacketFilters(ConnectionSide side, Iterable<Integer> packets) {
if (side == null)
throw new IllegalArgumentException("side cannot be NULL.");
for (int packetID : packets) {
if (side.isForServer())
playerInjection.removePacketHandler(packetID);
if (side.isForClient() && packetInjector != null)
packetInjector.removePacketHandler(packetID);
}
}
@Override
public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException {
sendServerPacket(reciever, packet, true);
}
@Override
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
if (reciever == null)
throw new IllegalArgumentException("reciever cannot be NULL.");
if (packet == null)
throw new IllegalArgumentException("packet cannot be NULL.");
// We may have to enable player injection indefinitely after this
if (packetCreation.compareAndSet(false, true))
incrementPhases(GamePhase.PLAYING);
playerInjection.sendServerPacket(reciever, packet, filters);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, true);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
if (sender == null)
throw new IllegalArgumentException("sender cannot be NULL.");
if (packet == null)
throw new IllegalArgumentException("packet cannot be NULL.");
// And here too
if (packetCreation.compareAndSet(false, true))
incrementPhases(GamePhase.PLAYING);
Packet mcPacket = packet.getHandle();
// Make sure the packet isn't cancelled
packetInjector.undoCancel(packet.getID(), mcPacket);
if (filters) {
PacketEvent event = packetInjector.packetRecieved(packet, sender);
if (!event.isCancelled())
mcPacket = event.getPacket().getHandle();
else
return;
}
playerInjection.processPacket(sender, mcPacket);
}
@Override
public PacketContainer createPacket(int id) {
return createPacket(id, true);
}
@Override
public PacketContainer createPacket(int id, boolean forceDefaults) {
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;
}
@Override
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
return PacketConstructor.DEFAULT.withPacket(id, arguments);
}
@Override
public Set<Integer> getSendingFilters() {
return playerInjection.getSendingFilters();
}
@Override
public Set<Integer> getReceivingFilters() {
return ImmutableSet.copyOf(packetInjector.getPacketHandlers());
}
@Override
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
EntityUtilities.updateEntity(entity, observers);
}
/**
* Initialize the packet injection for every player.
* @param players - list of players to inject.
*/
public void initializePlayers(Player[] players) {
for (Player player : players)
playerInjection.injectPlayer(player);
}
/**
* Uninitialize the packet injection of every player.
* @param players - list of players to uninject.
*/
public void uninitializePlayers(Player[] players) {
for (Player player : players)
playerInjection.uninjectPlayer(player);
}
/**
* Register this protocol manager on Bukkit.
* @param manager - Bukkit plugin manager that provides player join/leave events.
* @param plugin - the parent plugin.
*/
public void registerEvents(PluginManager manager, final Plugin plugin) {
try {
manager.registerEvents(new Listener() {
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onPrePlayerJoin(PlayerJoinEvent event) {
try {
// Let's clean up the other injection first.
playerInjection.uninjectPlayer(event.getPlayer().getAddress());
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject net handler for player.", e, event);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent event) {
try {
// This call will be ignored if no listeners are registered
playerInjection.injectPlayer(event.getPlayer());
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerQuit(PlayerQuitEvent event) {
try {
playerInjection.handleDisconnect(event.getPlayer());
playerInjection.uninjectPlayer(event.getPlayer());
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject logged off player.", e, event);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPluginDisabled(PluginDisableEvent event) {
try {
// Clean up in case the plugin forgets
if (event.getPlugin() != plugin) {
removePacketListeners(event.getPlugin());
}
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this, "Unable handle disabled plugin.", e, event);
}
}
}, plugin);
} catch (NoSuchMethodError e) {
// Oh wow! We're running on 1.0.0 or older.
registerOld(manager, plugin);
}
}
/**
* Retrieve the number of listeners that expect packets during playing.
* @return Number of listeners.
*/
private int getPhasePlayingCount() {
return phasePlayingCount.get();
}
/**
* Retrieve the number of listeners that expect packets during login.
* @return Number of listeners
*/
private int getPhaseLoginCount() {
return phaseLoginCount.get();
}
@Override
public int getPacketID(Packet packet) {
if (packet == null)
throw new IllegalArgumentException("Packet cannot be NULL.");
return MinecraftRegistry.getPacketToID().get(packet.getClass());
}
@Override
public void registerPacketClass(Class<?> clazz, int packetID) {
MinecraftRegistry.getPacketToID().put(clazz, packetID);
}
@Override
public void unregisterPacketClass(Class<?> clazz) {
MinecraftRegistry.getPacketToID().remove(clazz);
}
@Override
public Class<?> getPacketClassFromID(int packetID, boolean forceVanilla) {
return MinecraftRegistry.getPacketClassFromID(packetID, forceVanilla);
}
// Yes, this is crazy.
@SuppressWarnings({ "unchecked", "rawtypes" })
private void registerOld(PluginManager manager, Plugin plugin) {
try {
ClassLoader loader = manager.getClass().getClassLoader();
// The different enums we are going to need
Class eventTypes = loader.loadClass("org.bukkit.event.Event$Type");
Class eventPriority = loader.loadClass("org.bukkit.event.Event$Priority");
// Get the priority
Object priorityMonitor = Enum.valueOf(eventPriority, "Monitor");
// Get event types
Object playerJoinType = Enum.valueOf(eventTypes, "PLAYER_JOIN");
Object playerQuitType = Enum.valueOf(eventTypes, "PLAYER_QUIT");
Object pluginDisabledType = Enum.valueOf(eventTypes, "PLUGIN_DISABLE");
// The player listener! Good times.
Class<?> playerListener = loader.loadClass("org.bukkit.event.player.PlayerListener");
Class<?> serverListener = loader.loadClass("org.bukkit.event.server.ServerListener");
// Find the register event method
Method registerEvent = FuzzyReflection.fromObject(manager).getMethodByParameters("registerEvent",
eventTypes, Listener.class, eventPriority, Plugin.class);
Enhancer playerEx = new Enhancer();
Enhancer serverEx = new Enhancer();
playerEx.setSuperclass(playerListener);
playerEx.setClassLoader(classLoader);
playerEx.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// Must have a parameter
if (args.length == 1) {
Object event = args[0];
// Check for the correct event
if (event instanceof PlayerJoinEvent) {
Player player = ((PlayerJoinEvent) event).getPlayer();
playerInjection.injectPlayer(player);
} else if (event instanceof PlayerQuitEvent) {
Player player = ((PlayerQuitEvent) event).getPlayer();
playerInjection.handleDisconnect(player);
playerInjection.uninjectPlayer(player);
}
}
return null;
}
});
serverEx.setSuperclass(serverListener);
serverEx.setClassLoader(classLoader);
serverEx.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
// Must have a parameter
if (args.length == 1) {
Object event = args[0];
if (event instanceof PluginDisableEvent)
removePacketListeners(((PluginDisableEvent) event).getPlugin());
}
return null;
}
});
// Create our listener
Object playerProxy = playerEx.create();
Object serverProxy = serverEx.create();
registerEvent.invoke(manager, playerJoinType, playerProxy, priorityMonitor, plugin);
registerEvent.invoke(manager, playerQuitType, playerProxy, priorityMonitor, plugin);
registerEvent.invoke(manager, pluginDisabledType, serverProxy, priorityMonitor, plugin);
// A lot can go wrong
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* Retrieve every known and supported server packet.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
public static Set<Integer> getServerPackets() throws FieldAccessException {
return MinecraftRegistry.getServerPackets();
}
/**
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public static Set<Integer> getClientPackets() throws FieldAccessException {
return MinecraftRegistry.getClientPackets();
}
/**
* Retrieves the current plugin class loader.
* @return Class loader.
*/
public ClassLoader getClassLoader() {
return classLoader;
}
@Override
public boolean isClosed() {
return hasClosed;
}
/**
* Called when ProtocolLib is closing.
*/
public void close() {
// Guard
if (hasClosed)
return;
// Remove packet handlers
if (packetInjector != null)
packetInjector.cleanupAll();
// Remove server handler
playerInjection.close();
hasClosed = true;
// Remove listeners
packetListeners.clear();
// Clean up async handlers. We have to do this last.
asyncFilterManager.cleanupAll();
}
@Override
protected void finalize() throws Throwable {
close();
}
}
@@ -1,249 +0,0 @@
/*
* 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 java.io.DataInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
/**
* This class is responsible for adding or removing proxy objects that intercepts recieved packets.
*
* @author Kristian
*/
class PacketInjector {
// The "put" method that associates a packet ID with a packet class
private static Method putMethod;
private static Object intHashMap;
// The packet filter manager
private ListenerInvoker manager;
// Error reporter
private ErrorReporter reporter;
// Allows us to determine the sender
private PlayerInjectionHandler playerInjection;
// Allows us to look up read packet injectors
private Map<Integer, ReadPacketModifier> readModifier;
// Class loader
private ClassLoader classLoader;
public PacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
this.classLoader = classLoader;
this.manager = manager;
this.playerInjection = playerInjection;
this.reporter = reporter;
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
initialize();
}
/**
* Undo a packet cancel.
* @param id - the id of the packet.
* @param packet - packet to uncancel.
*/
public void undoCancel(Integer id, Packet packet) {
ReadPacketModifier modifier = readModifier.get(id);
// Cancelled packets are represented with NULL
if (modifier != null && modifier.getOverride(packet) == null) {
modifier.removeOverride(packet);
}
}
private void initialize() throws IllegalAccessException {
if (intHashMap == null) {
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
Field intHashMapField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
try {
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Minecraft is incompatible.", e);
}
// Now, get the "put" method.
putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class);
}
}
@SuppressWarnings("rawtypes")
public boolean addPacketHandler(int packetID) {
if (hasPacketHandler(packetID))
return false;
Enhancer ex = new Enhancer();
// Unfortunately, we can't easily distinguish between these two functions:
// * Object lookup(int par1)
// * Object removeObject(int par1)
// So, we'll use the classMapToInt registry instead.
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
Map<Class, Integer> registry = MinecraftRegistry.getPacketToID();
Class old = MinecraftRegistry.getPacketClassFromID(packetID);
// If this packet is not known
if (old == null) {
throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version.");
}
// Check for previous injections
if (!old.getName().startsWith("net.minecraft.")) {
throw new IllegalStateException("Packet " + packetID + " has already been injected.");
}
// Subclass the specific packet class
ex.setSuperclass(old);
ex.setCallbackType(ReadPacketModifier.class);
ex.setClassLoader(classLoader);
Class proxy = ex.createClass();
// Create the proxy handler
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter);
readModifier.put(packetID, modifier);
// Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier });
try {
// Override values
previous.put(packetID, old);
registry.put(proxy, packetID);
overwritten.put(packetID, proxy);
putMethod.invoke(intHashMap, packetID, proxy);
return true;
} catch (IllegalArgumentException e) {
throw new RuntimeException("Illegal argument.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
}
@SuppressWarnings("rawtypes")
public boolean removePacketHandler(int packetID) {
if (!hasPacketHandler(packetID))
return false;
Map<Class, Integer> registry = MinecraftRegistry.getPacketToID();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
// Use the old class definition
try {
Class old = previous.get(packetID);
Class proxy = MinecraftRegistry.getPacketClassFromID(packetID);
putMethod.invoke(intHashMap, packetID, old);
previous.remove(packetID);
readModifier.remove(packetID);
registry.remove(proxy);
overwritten.remove(packetID);
return true;
// Handle some problems
} catch (IllegalArgumentException e) {
return false;
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
}
public boolean hasPacketHandler(int packetID) {
return MinecraftRegistry.getPreviousPackets().containsKey(packetID);
}
public Set<Integer> getPacketHandlers() {
return MinecraftRegistry.getPreviousPackets().keySet();
}
// Called from the ReadPacketModified monitor
PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
Player client = playerInjection.getPlayerByConnection(input);
// Never invoke a event if we don't know where it's from
if (client != null)
return packetRecieved(packet, client);
else
return null;
}
/**
* Let the packet listeners process the given packet.
* @param packet - a packet to process.
* @param client - the client that sent the packet.
* @return The resulting packet event.
*/
public PacketEvent packetRecieved(PacketContainer packet, Player client) {
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client);
manager.invokePacketRecieving(event);
return event;
}
@SuppressWarnings("rawtypes")
public synchronized void cleanupAll() {
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
// Remove every packet handler
for (Integer id : previous.keySet().toArray(new Integer[0])) {
removePacketHandler(id);
}
overwritten.clear();
previous.clear();
}
/**
* Inform the current PlayerInjector that it should update the DataInputStream next.
* @param player - the player to update.
*/
public void scheduleDataInputRefresh(Player player) {
playerInjection.scheduleDataInputRefresh(player);
}
}
@@ -1,145 +0,0 @@
/*
* 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 java.io.DataInputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
class ReadPacketModifier implements MethodInterceptor {
@SuppressWarnings("rawtypes")
private static Class[] parameters = { DataInputStream.class };
// A cancel marker
private static final Object CANCEL_MARKER = new Object();
// Common for all packets of the same type
private PacketInjector packetInjector;
private int packetID;
// Report errors
private ErrorReporter reporter;
// Whether or not a packet has been cancelled
private static Map<Object, Object> override = Collections.synchronizedMap(new WeakHashMap<Object, Object>());
public ReadPacketModifier(int packetID, PacketInjector packetInjector, ErrorReporter reporter) {
this.packetID = packetID;
this.packetInjector = packetInjector;
this.reporter = reporter;
}
/**
* Remove any packet overrides.
* @param packet - the packet to rever
*/
public void removeOverride(Packet packet) {
override.remove(packet);
}
/**
* Retrieve the packet that overrides the methods of the given packet.
* @param packet - the given packet.
* @return Overriden object.
*/
public Object getOverride(Packet packet) {
return override.get(packet);
}
@Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object returnValue = null;
String methodName = method.getName();
// We always pass these down (otherwise, we'll end up with a infinite loop)
if (methodName.equals("hashCode") || methodName.equals("equals") || methodName.equals("toString")) {
return proxy.invokeSuper(thisObj, args);
}
// Atomic retrieval
Object overridenObject = override.get(thisObj);
if (overridenObject != null) {
// This packet has been cancelled
if (overridenObject == CANCEL_MARKER) {
// So, cancel all void methods
if (method.getReturnType().equals(Void.TYPE))
return null;
else // Revert to normal for everything else
overridenObject = thisObj;
}
returnValue = proxy.invokeSuper(overridenObject, args);
} else {
returnValue = proxy.invokeSuper(thisObj, args);
}
// Is this a readPacketData method?
if (returnValue == null &&
Arrays.equals(method.getParameterTypes(), parameters)) {
try {
// We need this in order to get the correct player
DataInputStream input = (DataInputStream) args[0];
// Let the people know
PacketContainer container = new PacketContainer(packetID, (Packet) thisObj);
PacketEvent event = packetInjector.packetRecieved(container, input);
// Handle override
if (event != null) {
Packet result = event.getPacket().getHandle();
if (event.isCancelled()) {
override.put(thisObj, CANCEL_MARKER);
} else if (!objectEquals(thisObj, result)) {
override.put(thisObj, result);
}
// Update DataInputStream next time
if (!event.isCancelled() && packetID == Packets.Server.KEY_RESPONSE) {
packetInjector.scheduleDataInputRefresh(event.getPlayer());
}
}
} catch (Throwable e) {
// Minecraft cannot handle this error
reporter.reportDetailed(this, "Cannot handle clienet packet.", e, args[0]);
}
}
return returnValue;
}
private boolean objectEquals(Object a, Object b) {
return System.identityHashCode(a) != System.identityHashCode(b);
}
}
@@ -1,77 +0,0 @@
/*
* 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 java.util.Collection;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
/**
* Registry of synchronous packet listeners.
*
* @author Kristian
*/
class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
/**
* Invokes the given packet event for every registered listener.
* @param reporter - the error reporter that will be used to inform about listener exceptions.
* @param event - the packet event to invoke.
*/
public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
if (list == null)
return;
// The returned list is thread-safe
for (PrioritizedListener<PacketListener> element : list) {
try {
element.getListener().onPacketReceiving(event);
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving()", e);
}
}
}
/**
* Invokes the given packet event for every registered listener.
* @param reporter - the error reporter that will be used to inform about listener exceptions.
* @param event - the packet event to invoke.
*/
public void invokePacketSending(ErrorReporter reporter, PacketEvent event) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
if (list == null)
return;
for (PrioritizedListener<PacketListener> element : list) {
try {
element.getListener().onPacketSending(event);
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending()", e);
}
}
}
}
@@ -1,113 +0,0 @@
/*
* 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 java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.minecraft.server.Packet;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.compiler.CompileListener;
import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier;
/**
* Caches structure modifiers.
* @author Kristian
*/
public class StructureCache {
// Structure modifiers
private static ConcurrentMap<Integer, StructureModifier<Object>> structureModifiers =
new ConcurrentHashMap<Integer, StructureModifier<Object>>();
private static Set<Integer> compiling = new HashSet<Integer>();
/**
* Creates an empty Minecraft packet of the given ID.
* @param id - packet ID.
* @return Created packet.
*/
public static Packet newPacket(int id) {
try {
return (Packet) MinecraftRegistry.getPacketClassFromID(id, true).newInstance();
} catch (InstantiationException e) {
return null;
} catch (IllegalAccessException e) {
throw new RuntimeException("Access denied.", e);
}
}
/**
* Retrieve a cached structure modifier for the given packet id.
* @param id - packet ID.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(int id) {
// Compile structures by default
return getStructure(id, true);
}
/**
* Retrieve a cached structure modifier for the given packet id.
* @param id - packet ID.
* @param compile - whether or not to asynchronously compile the structure modifier.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(int id, boolean compile) {
StructureModifier<Object> result = structureModifiers.get(id);
// We don't want to create this for every lookup
if (result == null) {
// Use the vanilla class definition
final StructureModifier<Object> value = new StructureModifier<Object>(
MinecraftRegistry.getPacketClassFromID(id, true), Packet.class, true);
result = structureModifiers.putIfAbsent(id, value);
// We may end up creating multiple modifiers, but we'll agree on which to use
if (result == null) {
result = value;
}
}
// Automatically compile the structure modifier
if (compile && !(result instanceof CompiledStructureModifier)) {
// Compilation is many orders of magnitude slower than synchronization
synchronized (compiling) {
final int idCopy = id;
final BackgroundCompiler compiler = BackgroundCompiler.getInstance();
if (!compiling.contains(id) && compiler != null) {
compiler.scheduleCompilation(result, new CompileListener<Object>() {
@Override
public void onCompiled(StructureModifier<Object> compiledModifier) {
structureModifiers.put(idCopy, compiledModifier);
}
});
compiling.add(id);
}
}
}
return result;
}
}
@@ -1,297 +0,0 @@
/*
* 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.player;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.server.NetLoginHandler;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectCloner;
import com.comphenix.protocol.reflect.VolatileField;
/**
* Used to ensure that the 1.3 server is referencing the correct server handler.
*
* @author Kristian
*/
class InjectedServerConnection {
private static Field listenerThreadField;
private static Field minecraftServerField;
private static Field listField;
private static Field dedicatedThreadField;
private static Method serverConnectionMethod;
private List<VolatileField> listFields;
private List<ReplacedArrayList<Object>> replacedLists;
// Used to inject net handlers
private NetLoginInjector netLoginInjector;
private Server server;
private ErrorReporter reporter;
private boolean hasAttempted;
private boolean hasSuccess;
private Object minecraftServer = null;
public InjectedServerConnection(ErrorReporter reporter, Server server, NetLoginInjector netLoginInjector) {
this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
this.reporter = reporter;
this.server = server;
this.netLoginInjector = netLoginInjector;
}
public void injectList() {
// Only execute this method once
if (!hasAttempted)
hasAttempted = true;
else
return;
if (minecraftServerField == null)
minecraftServerField = FuzzyReflection.fromObject(server, true).getFieldByType(".*MinecraftServer");
try {
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) {
reporter.reportWarning(this, "Cannot extract minecraft server from Bukkit.");
return;
}
try {
if (serverConnectionMethod == null)
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
getMethodByParameters("getServerConnection", ".*ServerConnection", new String[] {});
// We're using Minecraft 1.3.1
injectServerConnection();
} catch (IllegalArgumentException e) {
// Minecraft 1.2.5 or lower
injectListenerThread();
} catch (Exception e) {
// Oh damn - inform the player
reporter.reportDetailed(this, "Cannot inject into server connection. Bad things will happen.", e);
}
}
private void injectListenerThread() {
try {
if (listenerThreadField == null)
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
getFieldByType(".*NetworkListenThread");
} catch (RuntimeException e) {
reporter.reportDetailed(this, "Cannot find listener thread in MinecraftServer.", e, minecraftServer);
return;
}
Object listenerThread = null;
// Attempt to get the thread
try {
listenerThread = listenerThreadField.get(minecraftServer);
} catch (Exception e) {
reporter.reportWarning(this, "Unable to read the listener thread.", e);
return;
}
// Just inject every list field we can get
injectEveryListField(listenerThread, 1);
hasSuccess = true;
}
private void injectServerConnection() {
Object serverConnection = null;
// Careful - we might fail
try {
serverConnection = serverConnectionMethod.invoke(minecraftServer);
} catch (Exception ex) {
reporter.reportDetailed(this, "Unable to retrieve server connection", ex, minecraftServer);
return;
}
if (listField == null)
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
getFieldByType("netServerHandlerList", List.class);
if (dedicatedThreadField == null) {
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(Thread.class);
// Verify the field count
if (matches.size() != 1)
reporter.reportWarning(this, "Unexpected number of threads in " + serverConnection.getClass().getName());
else
dedicatedThreadField = matches.get(0);
}
// Next, try to get the dedicated thread
try {
if (dedicatedThreadField != null)
injectEveryListField(FieldUtils.readField(dedicatedThreadField, serverConnection, true), 1);
} catch (IllegalAccessException e) {
reporter.reportWarning(this, "Unable to retrieve net handler thread.", e);
}
injectIntoList(serverConnection, listField);
hasSuccess = true;
}
/**
* Automatically inject into every List-compatible public or private field of the given object.
* @param container - container object with the fields to inject.
* @param minimum - the minimum number of fields we expect exists.
*/
private void injectEveryListField(Object container, int minimum) {
// Ok, great. Get every list field
List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
for (Field list : lists) {
injectIntoList(container, list);
}
// Warn about unexpected errors
if (lists.size() < minimum) {
reporter.reportWarning(this, "Unable to inject " + minimum + " lists in " + container.getClass().getName());
}
}
@SuppressWarnings("unchecked")
private void injectIntoList(Object instance, Field field) {
VolatileField listFieldRef = new VolatileField(field, instance, true);
List<Object> list = (List<Object>) listFieldRef.getValue();
// Careful not to inject twice
if (list instanceof ReplacedArrayList) {
replacedLists.add((ReplacedArrayList<Object>) list);
} else {
ReplacedArrayList<Object> injectedList = createReplacement(list);
replacedLists.add(injectedList);
listFieldRef.setValue(injectedList);
listFields.add(listFieldRef);
}
}
// Hack to avoid the "moved to quickly" error
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
return new ReplacedArrayList<Object>(list) {
/**
* Shut up Eclipse!
*/
private static final long serialVersionUID = 2070481080950500367L;
@Override
protected void onReplacing(Object inserting, Object replacement) {
// Is this a normal Minecraft object?
if (!(inserting instanceof Factory)) {
// If so, copy the content of the old element to the new
try {
ObjectCloner.copyTo(inserting, replacement, inserting.getClass());
} catch (Throwable e) {
reporter.reportDetailed(InjectedServerConnection.this, "Cannot copy old " + inserting +
" to new.", e, inserting, replacement);
}
}
}
@Override
protected void onInserting(Object inserting) {
// Ready for some login handler injection?
if (inserting instanceof NetLoginHandler) {
Object replaced = netLoginInjector.onNetLoginCreated(inserting);
// Only replace if it has changed
if (inserting != replaced)
addMapping(inserting, replaced, true);
}
}
@Override
protected void onRemoved(Object removing) {
// Clean up?
if (removing instanceof NetLoginHandler) {
netLoginInjector.cleanup(removing);
}
}
};
}
/**
* Replace the server handler instance kept by the "keep alive" object.
* @param oldHandler - old server handler.
* @param newHandler - new, proxied server handler.
*/
public void replaceServerHandler(Object oldHandler, Object newHandler) {
if (!hasAttempted) {
injectList();
}
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.addMapping(oldHandler, newHandler);
}
}
}
/**
* Revert to the old vanilla server handler, if it has been replaced.
* @param oldHandler - old vanilla server handler.
*/
public void revertServerHandler(Object oldHandler) {
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.removeMapping(oldHandler);
}
}
}
/**
* Undoes everything.
*/
public void cleanupAll() {
if (replacedLists.size() > 0) {
// Repair the underlying lists
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.revertAll();
}
for (VolatileField field : listFields) {
field.revertValue();
}
listFields.clear();
replacedLists.clear();
}
}
}
@@ -1,76 +0,0 @@
package com.comphenix.protocol.injector.player;
import java.util.Arrays;
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
*/
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];
}
/**
* Determine whether or not the given element exists in the set.
* @param value - 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 OutOfBoundsException If the given element is not in the range [0, count).
*/
public void add(int element) {
array[element] = true;
}
/**
* 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;
}
}
@@ -1,139 +0,0 @@
package com.comphenix.protocol.injector.player;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer;
import com.google.common.collect.Maps;
/**
* Injects every NetLoginHandler created by the server.
*
* @author Kristian
*/
class NetLoginInjector {
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
// Handles every hook
private PlayerInjectionHandler injectionHandler;
private Server server;
// The current error rerporter
private ErrorReporter reporter;
private ReadWriteLock injectionLock = new ReentrantReadWriteLock();
// Used to create fake players
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
public NetLoginInjector(ErrorReporter reporter, PlayerInjectionHandler injectionHandler, Server server) {
this.reporter = reporter;
this.injectionHandler = injectionHandler;
this.server = server;
}
/**
* Invoked when a NetLoginHandler has been created.
* @param inserting - the new NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
*/
public Object onNetLoginCreated(Object inserting) {
injectionLock.writeLock().lock();
try {
// Make sure we actually need to inject during this phase
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
return inserting;
Player fakePlayer = tempPlayerFactory.createTemporaryPlayer(server);
PlayerInjector injector = injectionHandler.injectPlayer(fakePlayer, inserting, GamePhase.LOGIN);
injector.updateOnLogin = true;
// Associate the injector too
InjectContainer container = (InjectContainer) fakePlayer;
container.setInjector(injector);
// Save the login
injectedLogins.putIfAbsent(inserting, injector);
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
return inserting;
} catch (Throwable e) {
// Minecraft can't handle this, so we'll deal with it here
reporter.reportDetailed(this, "Unable to hook NetLoginHandler.", e, inserting);
return inserting;
} finally {
injectionLock.writeLock().unlock();
}
}
/**
* Retrieve the lock used for reading.
* @return Reading lock.
*/
public Lock getReadLock() {
return injectionLock.readLock();
}
/**
* Invoked when a NetLoginHandler should be reverted.
* @param inserting - the original NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
*/
public synchronized void cleanup(Object removing) {
PlayerInjector injected = injectedLogins.get(removing);
if (injected != null) {
try {
PlayerInjector newInjector = null;
Player player = injected.getPlayer();
// Clean up list
injectedLogins.remove(removing);
// No need to clean up twice
if (injected.isClean())
return;
// Hack to clean up other references
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
// Update NetworkManager
if (newInjector == null) {
injectionHandler.uninjectPlayer(player);
} else {
injectionHandler.uninjectPlayer(player, false);
if (injected instanceof NetworkObjectInjector)
newInjector.setNetworkManager(injected.getNetworkManager(), true);
}
} catch (Throwable e) {
// Don't leak this to Minecraft
reporter.reportDetailed(this, "Cannot cleanup NetLoginHandler.", e, removing);
}
}
}
/**
* Remove all injected hooks.
*/
public void cleanupAll() {
for (PlayerInjector injector : injectedLogins.values()) {
injector.cleanupAll();
}
injectedLogins.clear();
}
}
@@ -1,163 +0,0 @@
package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.FieldAccessException;
/**
* Create fake player instances that represents pre-authenticated clients.
*/
class TemporaryPlayerFactory {
/**
* Able to store a PlayerInjector.
* <p>
* A necessary hack.
* @author Kristian
*/
public static class InjectContainer {
private PlayerInjector injector;
public PlayerInjector getInjector() {
return injector;
}
public void setInjector(PlayerInjector injector) {
this.injector = injector;
}
}
// Helpful constructors
private final PacketConstructor chatPacket;
// Prevent too many class creations
private static CallbackFilter callbackFilter;
public TemporaryPlayerFactory() {
chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" });
}
/**
* Construct a temporary player that supports a subset of every player command.
* <p>
* Supported methods include:
* <ul>
* <li>getPlayer()</li>
* <li>getAddress()</li>
* <li>getServer()</li>
* <li>chat(String)</li>
* <li>sendMessage(String)</li>
* <li>sendMessage(String[])</li>
* <li>kickPlayer(String)</li>
* </ul>
* <p>
* Note that the player a player has not been assigned a name yet, and thus cannot be
* uniquely identified. Use the
* @param injector - the player injector used.
* @param server - the current server.
* @return A temporary player instance.
*/
public Player createTemporaryPlayer(final Server server) {
// Default implementation
Callback implementation = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName();
PlayerInjector injector = ((InjectContainer) obj).getInjector();
if (injector == null)
throw new IllegalStateException("Unable to find injector.");
// Use the socket to get the address
if (methodName.equalsIgnoreCase("getName"))
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
if (methodName.equalsIgnoreCase("getPlayer"))
return injector.getUpdatedPlayer();
if (methodName.equalsIgnoreCase("getAddress"))
return injector.getAddress();
if (methodName.equalsIgnoreCase("getServer"))
return server;
try {
// Handle send message methods
if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) {
Object argument = args[0];
// Dynamic overloading
if (argument instanceof String) {
return sendMessage(injector, (String) argument);
} else if (argument instanceof String[]) {
for (String message : (String[]) argument) {
sendMessage(injector, message);
}
return null;
}
}
} catch (InvocationTargetException e) {
throw e.getCause();
}
// Also, handle kicking
if (methodName.equalsIgnoreCase("kickPlayer")) {
injector.disconnect((String) args[0]);
return null;
}
// Ignore all other methods
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for temporary players.");
}
};
// Shared callback filter
if (callbackFilter == null) {
callbackFilter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Do not override the object method or the superclass methods
if (method.getDeclaringClass().equals(Object.class) ||
method.getDeclaringClass().equals(InjectContainer.class))
return 0;
else
return 1;
}
};
}
// CGLib is amazing
Enhancer ex = new Enhancer();
ex.setSuperclass(InjectContainer.class);
ex.setInterfaces(new Class[] { Player.class });
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
ex.setCallbackFilter(callbackFilter);
return (Player) ex.create();
}
/**
* Send a message to the given client.
* @param injector - the injector representing the client.
* @param message - a message.
* @return Always NULL.
* @throws InvocationTargetException If the message couldn't be sent.
* @throws FieldAccessException If we were unable to construct the message packet.
*/
private Object sendMessage(PlayerInjector injector, String message) throws InvocationTargetException, FieldAccessException {
injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
return null;
}
}
@@ -1,87 +0,0 @@
/*
* 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.reflect;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Can copy an object field by field.
*
* @author Kristian
*/
public class ObjectCloner {
// Cache structure modifiers
@SuppressWarnings("rawtypes")
private static ConcurrentMap<Class, StructureModifier<Object>> cache =
new ConcurrentHashMap<Class, StructureModifier<Object>>();
/**
* Copy every field in object A to object B.
* <p>
* The two objects must have the same number of fields of the same type.
* @param source - fields to copy.
* @param destination - fields to copy to.
* @param commonType - type containing each field to copy.
*/
public static void copyTo(Object source, Object destination, Class<?> commonType) {
if (source == null)
throw new IllegalArgumentException("Source cannot be NULL");
if (destination == null)
throw new IllegalArgumentException("Destination cannot be NULL");
StructureModifier<Object> modifier = cache.get(commonType);
// Create the structure modifier if we haven't already
if (modifier == null) {
StructureModifier<Object> value = new StructureModifier<Object>(commonType, null, false);
modifier = cache.putIfAbsent(commonType, value);
if (modifier == null)
modifier = value;
}
// Add target
StructureModifier<Object> modifierSource = modifier.withTarget(source);
StructureModifier<Object> modifierDest = modifier.withTarget(destination);
// Copy every field
try {
for (int i = 0; i < modifierSource.size(); i++) {
Object value = modifierSource.read(i);
modifierDest.write(i, value);
// System.out.println(String.format("Writing value %s to %s",
// value, modifier.getFields().get(i).getName()));
}
// Copy private fields underneath
Class<?> superclass = commonType.getSuperclass();
if (!superclass.equals(Object.class)) {
copyTo(source, destination, superclass);
}
} catch (FieldAccessException e) {
throw new RuntimeException("Unable to copy fields from " + commonType.getName(), e);
}
}
}
@@ -1,124 +0,0 @@
package com.comphenix.protocol.reflect;
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Contains static utility methods pertaining to primitive types and their
* corresponding wrapper types.
*
* @author Kevin Bourrillion
*/
public final class PrimitiveUtils {
private PrimitiveUtils() {
}
/** A map from primitive types to their corresponding wrapper types. */
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER_TYPE;
/** A map from wrapper types to their corresponding primitive types. */
private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE_TYPE;
// Sad that we can't use a BiMap. :(
static {
Map<Class<?>, Class<?>> primToWrap = new HashMap<Class<?>, Class<?>>(16);
Map<Class<?>, Class<?>> wrapToPrim = new HashMap<Class<?>, Class<?>>(16);
add(primToWrap, wrapToPrim, boolean.class, Boolean.class);
add(primToWrap, wrapToPrim, byte.class, Byte.class);
add(primToWrap, wrapToPrim, char.class, Character.class);
add(primToWrap, wrapToPrim, double.class, Double.class);
add(primToWrap, wrapToPrim, float.class, Float.class);
add(primToWrap, wrapToPrim, int.class, Integer.class);
add(primToWrap, wrapToPrim, long.class, Long.class);
add(primToWrap, wrapToPrim, short.class, Short.class);
add(primToWrap, wrapToPrim, void.class, Void.class);
PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap);
WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim);
}
private static void add(Map<Class<?>, Class<?>> forward,
Map<Class<?>, Class<?>> backward, Class<?> key, Class<?> value) {
forward.put(key, value);
backward.put(value, key);
}
/**
* Returns true if this type is a primitive.
*/
public static boolean isPrimitive(Type type) {
return PRIMITIVE_TO_WRAPPER_TYPE.containsKey(type);
}
/**
* Returns {@code true} if {@code type} is one of the nine primitive-wrapper
* types, such as {@link Integer}.
*
* @see Class#isPrimitive
*/
public static boolean isWrapperType(Type type) {
return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(checkNotNull(type));
}
/**
* Returns the corresponding wrapper type of {@code type} if it is a
* primitive type; otherwise returns {@code type} itself. Idempotent.
*
* <pre>
* wrap(int.class) == Integer.class
* wrap(Integer.class) == Integer.class
* wrap(String.class) == String.class
* </pre>
*/
public static <T> Class<T> wrap(Class<T> type) {
// cast is safe: long.class and Long.class are both of type Class<Long>
@SuppressWarnings("unchecked")
Class<T> wrapped = (Class<T>) PRIMITIVE_TO_WRAPPER_TYPE
.get(checkNotNull(type));
return (wrapped == null) ? type : wrapped;
}
/**
* Returns the corresponding primitive type of {@code type} if it is a
* wrapper type; otherwise returns {@code type} itself. Idempotent.
*
* <pre>
* unwrap(Integer.class) == int.class
* unwrap(int.class) == int.class
* unwrap(String.class) == String.class
* </pre>
*/
public static <T> Class<T> unwrap(Class<T> type) {
// cast is safe: long.class and Long.class are both of type Class<Long>
@SuppressWarnings("unchecked")
Class<T> unwrapped = (Class<T>) WRAPPER_TO_PRIMITIVE_TYPE
.get(checkNotNull(type));
return (unwrapped == null) ? type : unwrapped;
}
public static <T> T checkNotNull(T obj) {
if (obj == null) {
throw new NullPointerException();
}
return obj;
}
}
@@ -1,200 +0,0 @@
/*
* 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.reflect.compiler;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.comphenix.protocol.reflect.StructureModifier;
/**
* Compiles structure modifiers on a background thread.
* <p>
* This is necessary as we cannot block the main thread.
*
* @author Kristian
*/
public class BackgroundCompiler {
// How long to wait for a shutdown
public static final int SHUTDOWN_DELAY_MS = 2000;
// The single background compiler we're using
private static BackgroundCompiler backgroundCompiler;
private StructureCompiler compiler;
private boolean enabled;
private boolean shuttingDown;
private ExecutorService executor;
/**
* Retrieves the current background compiler.
* @return Current background compiler.
*/
public static BackgroundCompiler getInstance() {
return backgroundCompiler;
}
/**
* Sets the single background compiler we're using.
* @param backgroundCompiler - current background compiler, or NULL if the library is not loaded.
*/
public static void setInstance(BackgroundCompiler backgroundCompiler) {
BackgroundCompiler.backgroundCompiler = backgroundCompiler;
}
/**
* Initialize a background compiler.
* @param loader - class loader from Bukkit.
*/
public BackgroundCompiler(ClassLoader loader) {
this(loader, Executors.newSingleThreadExecutor());
}
/**
* Initialize a background compiler utilizing the given thread pool.
* @param loader - class loader from Bukkit.
* @param executor - thread pool we'll use.
*/
public BackgroundCompiler(ClassLoader loader, ExecutorService executor) {
if (loader == null)
throw new IllegalArgumentException("loader cannot be NULL");
if (executor == null)
throw new IllegalArgumentException("executor cannot be NULL");
this.compiler = new StructureCompiler(loader);
this.executor = executor;
this.enabled = true;
}
/**
* Ensure that the indirectly given structure modifier is eventually compiled.
* @param cache - store of structure modifiers.
* @param key - key of the structure modifier to compile.
*/
@SuppressWarnings("rawtypes")
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) {
@SuppressWarnings("unchecked")
final StructureModifier<Object> uncompiled = cache.get(key);
if (uncompiled != null) {
scheduleCompilation(uncompiled, new CompileListener<Object>() {
@Override
public void onCompiled(StructureModifier<Object> compiledModifier) {
// Update cache
cache.put(key, compiledModifier);
}
});
}
}
/**
* Ensure that the given structure modifier is eventually compiled.
* @param uncompiled - structure modifier to compile.
* @param listener - listener responsible for responding to the compilation.
*/
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
// Only schedule if we're enabled
if (enabled && !shuttingDown) {
// Don't try to schedule anything
if (executor == null || executor.isShutdown())
return;
try {
executor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
StructureModifier<TKey> modifier = uncompiled;
// Do our compilation
modifier = compiler.compile(modifier);
listener.onCompiled(modifier);
// We'll also return the new structure modifier
return modifier;
}
});
} catch (RejectedExecutionException e) {
// Occures when the underlying queue is overflowing. Since the compilation
// is only an optmization and not really essential we'll just log this failure
// and move on.
Logger.getLogger("Minecraft").log(Level.WARNING, "Unable to schedule compilation task.", e);
}
}
}
/**
* Clean up after ourselves using the default timeout.
*/
public void shutdownAll() {
shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
}
/**
* Clean up after ourselves.
* @param timeout - the maximum time to wait.
* @param unit - the time unit of the timeout argument.
*/
public void shutdownAll(long timeout, TimeUnit unit) {
setEnabled(false);
shuttingDown = true;
executor.shutdown();
try {
executor.awaitTermination(timeout, unit);
} catch (InterruptedException e) {
// Unlikely to ever occur.
e.printStackTrace();
}
}
/**
* Retrieve whether or not the background compiler is enabled.
* @return TRUE if it is enabled, FALSE otherwise.
*/
public boolean isEnabled() {
return enabled;
}
/**
* Sets whether or not the background compiler is enabled.
* @param enabled - TRUE to enable it, FALSE otherwise.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Retrieve the current structure compiler.
* @return Current structure compiler.
*/
public StructureCompiler getCompiler() {
return compiler;
}
}
-8
View File
@@ -1,8 +0,0 @@
name: ProtocolLib
version: 1.5.0
description: Provides read/write access to the Minecraft protocol.
author: Comphenix
website: http://www.comphenix.net/ProtocolLib
main: com.comphenix.protocol.ProtocolLibrary
database: false
@@ -1,26 +0,0 @@
package com.comphenix.protocol.reflect;
import static org.junit.Assert.*;
import net.minecraft.server.Packet103SetSlot;
import org.junit.Test;
import com.avaje.ebeaninternal.server.cluster.Packet;
import com.comphenix.protocol.reflect.StructureModifier;
public class StructureModifierTest {
@Test
public void test() throws FieldAccessException {
Packet103SetSlot move = new Packet103SetSlot();
StructureModifier<Object> modifier = new StructureModifier<Object>(
Packet103SetSlot.class, Packet.class, true);
move.a = 1;
int value = (Integer) modifier.withTarget(move).withType(int.class).read(0);
assertEquals(1, value);
}
}
+104 -80
View File
@@ -1,5 +1,4 @@
ProtocolLib
===========
# ProtocolLib [![Travis Status](https://travis-ci.org/dmulloy2/ProtocolLib.svg?branch=master)](https://travis-ci.org/dmulloy2/ProtocolLib)
Certain tasks are impossible to perform with the standard Bukkit API, and may require
working with and even modify Minecraft directly. A common technique is to modify incoming
@@ -11,129 +10,154 @@ Critically, different plugins that use this approach may _hook_ into the same cl
with unpredictable outcomes. More than often this causes plugins to crash, but it may also
lead to more subtle bugs.
Currently maintained by dmulloy2 on behalf of [Spigot](http://www.spigotmc.org/).
### Resources
* [JavaDoc](http://aadnk.github.com/ProtocolLib/Javadoc/)
* [Resource Page](http://www.spigotmc.org/resources/protocollib.1997/)
* [Dev Builds](http://ci.dmulloy2.net/job/ProtocolLib)
* [JavaDoc](http://ci.dmulloy2.net/job/ProtocolLib/javadoc)
### Compilation
Building
--------
You can compile this project yourself by using the latest version of Maven.
ProtocolLib is built with Maven and requires Spigot and SpigotAPI, which can be found [here](http://www.spigotmc.org/wiki/buildtools/).
A new API
---------
### A new API
__ProtocolLib__ attempts to solve this problem by providing a event API, much like Bukkit,
that allow plugins to monitor, modify or cancel packets sent and received. But more importantly,
that allows plugins to monitor, modify, or cancel packets sent and received. But, more importantly,
the API also hides all the gritty, obfuscated classes with a simple index based read/write system.
You no longer have to reference CraftBukkit!
### Using ProtocolLib
To use the library, first add ProtocolLib.jar to your Java build path. Then, add ProtocolLib
as a dependency (or soft-dependency, if you can live without it) to your plugin.yml file:
To use this library, first add ProtocolLib.jar to your Java build path. Then, add ProtocolLib
as a dependency or soft dependency to your plugin.yml file like any other plugin:
depends: [ProtocolLib]
````yml
depend: [ProtocolLib]
````
Future versions will be available in a public Maven repository, possibly on Maven central. But it
will always be possible to reference ProtocolLib manually.
You can also add ProtocolLib as a Maven dependency:
Then get a reference to ProtocolManager in onLoad() and you're good to go.
````xml
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>http://repo.dmulloy2.net/content/groups/public/</url>
</repository>
...
</repositories>
private ProtocolManager protocolManager;
public void onLoad() {
protocolManager = ProtocolLibrary.getProtocolManager();
}
<dependencies>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>3.6.5</version>
</dependency>
</dependencies>
````
Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go.
````java
private ProtocolManager protocolManager;
public void onLoad() {
protocolManager = ProtocolLibrary.getProtocolManager();
}
````
To listen for packets sent by the server to a client, add a server-side listener:
// Disable all sound effects
protocolManager.addPacketListener(
new PacketAdapter(this, ConnectionSide.SERVER_SIDE, ListenerPriority.NORMAL, 0x3E) {
@Override
public void onPacketSending(PacketEvent event) {
// Item packets
switch (event.getPacketID()) {
case 0x3E: // Sound effect
event.setCancelled(true);
break;
}
````java
// Disable all sound effects
protocolManager.addPacketListener(
new PacketAdapter(this, ListenerPriority.NORMAL,
PacketType.Play.Server.NAMED_SOUND_EFFECT) {
@Override
public void onPacketSending(PacketEvent event) {
// Item packets (id: 0x29)
if (event.getPacketType() ==
PacketType.Play.Server.NAMED_SOUND_EFFECT) {
event.setCancelled(true);
}
});
}
});
````
It's also possible to read and modify the content of these packets. For instance, you can create a global
censor by listening for Packet3Chat events:
// Censor
protocolManager.addPacketListener(
new PacketAdapter(this, ConnectionSide.CLIENT_SIDE, ListenerPriority.NORMAL, 0x03) {
@Override
public void onPacketReceiving(PacketEvent event) {
if (event.getPacketID() == 0x03) {
try {
PacketContainer packet = event.getPacket();
String message = packet.getSpecificModifier(String.class).read(0);
if (message.contains("shit") || message.contains("damn")) {
event.setCancelled(true);
event.getPlayer().sendMessage("Bad manners!");
}
} catch (FieldAccessException e) {
getLogger().log(Level.SEVERE, "Couldn't access field.", e);
}
````java
// Censor
protocolManager.addPacketListener(new PacketAdapter(this,
ListenerPriority.NORMAL,
PacketType.Play.Client.CHAT) {
@Override
public void onPacketReceiving(PacketEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CHAT) {
PacketContainer packet = event.getPacket();
String message = packet.getStrings().read(0);
if (message.contains("shit")
|| message.contains("damn")) {
event.setCancelled(true);
event.getPlayer().sendMessage("Bad manners!");
}
}
});
}
});
````
### Sending packets
Normally, you might have to do something ugly like the following:
Packet60Explosion fakeExplosion = new Packet60Explosion();
fakeExplosion.a = player.getLocation().getX();
fakeExplosion.b = player.getLocation().getY();
fakeExplosion.c = player.getLocation().getZ();
fakeExplosion.d = 3.0F;
fakeExplosion.e = new ArrayList<Object>();
````java
Packet60Explosion fakeExplosion = new Packet60Explosion();
fakeExplosion.a = player.getLocation().getX();
fakeExplosion.b = player.getLocation().getY();
fakeExplosion.c = player.getLocation().getZ();
fakeExplosion.d = 3.0F;
fakeExplosion.e = new ArrayList<Object>();
((CraftPlayer) player).getHandle().netServerHandler.sendPacket(fakeExplosion);
((CraftPlayer) player).getHandle().netServerHandler.sendPacket(fakeExplosion);
````
But with ProtocolLib, you can turn that into something more manageable. Notice that
you don't have to create an ArrayList this version:
you don't have to create an ArrayList with this version:
PacketContainer fakeExplosion = protocolManager.createPacket(60);
fakeExplosion.getSpecificModifier(double.class).
write(0, player.getLocation().getX()).
write(1, player.getLocation().getY()).
write(2, player.getLocation().getZ());
fakeExplosion.getSpecificModifier(float.class).
write(0, 3.0F);
````java
PacketContainer fakeExplosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
fakeExplosion.getDoubles().
write(0, player.getLocation().getX()).
write(1, player.getLocation().getY()).
write(2, player.getLocation().getZ());
fakeExplosion.getFloat().write(0, 3.0F);
try {
protocolManager.sendServerPacket(player, fakeExplosion);
} catch (InvocationTargetException e) {
throw new RuntimeException(
"Cannot send packet " + fakeExplosion, e);
}
````
Compatiblity
------------
### Compatibility
One of the main goals of this project was to achieve maximum compatibility with CraftBukkit. And the end
result is quite flexible - in tests I successfully ran an unmodified ProtocolLib on CraftBukkit 1.8.0, and
it should be resiliant against future changes. It's likely that I won't have to update ProtocolLib for
anything but bug and performance fixes.
result is quite flexible. Aside from netty package changes, it should be resilient against future changes.
It's likely that I won't have to update ProtocolLib for anything but bug fixes and new features.
How is this possible? It all comes down to reflection in the end. Essentially, no name is hard coded -
every field, method and class is deduced by looking at field types, package names or parameter
types. It's remarkably consistent across different versions.
### Incompatiblity
### Incompatibility
The following plugins (to be expanded) are not compatible with ProtocolLib:
* TagAPI
+252
View File
@@ -0,0 +1,252 @@
<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>
<artifactId>ProtocolLib-API</artifactId>
<name>ProtocolLib-API</name>
<version>4.2.0</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>v4-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.number></project.build.number>
<project.fullVersion>${project.version}</project.fullVersion>
</properties>
<build>
<defaultGoal>clean install</defaultGoal>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifestEntries>
<Main-Class>com.comphenix.protocol.Application</Main-Class>
<Implementation-Title>ProtocolLib</Implementation-Title>
<Implementation-Version>${project.fullVersion}</Implementation-Version>
<Implementation-Vendor>dmulloy2</Implementation-Vendor>
</manifestEntries>
</archive>
<finalName>ProtocolLib</finalName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.10</version>
<configuration>
<systemProperties>
<property>
<name>projectVersion</name>
<value>${project.version}</value>
</property>
</systemProperties>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>jenkins</id>
<activation>
<property>
<name>env.BUILD_NUMBER</name>
</property>
</activation>
<properties>
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
<project.fullVersion>${project.version}${project.build.number}</project.fullVersion>
</properties>
</profile>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<properties>
<project.fullVersion>${project.version}</project.fullVersion>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.0</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.3</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- TODO: Look into signing releases
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
-->
</plugins>
</build>
</profile>
</profiles>
<scm>
<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>
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dmulloy2</id>
<name>Dan Mulloy</name>
<url>http://dmulloy2.net/</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>former author</role>
</roles>
<timezone>1</timezone>
</developer>
</developers>
<dependencies>
<!-- Included with Minecraft
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.23.Final</version>
</dependency>
-->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.11.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.11.2-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>
@@ -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
*/
@@ -22,6 +22,7 @@ import java.util.Set;
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.
@@ -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,114 @@
/*
* 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 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.
*
* @author Kristian
*/
public interface PacketStream {
/**
* Send a packet to the given player.
* @param receiver - the reciever.
* @param packet - packet to send.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player receiver, PacketContainer packet)
throws InvocationTargetException;
/**
* Send a packet to the given player.
* @param receiver - the reciever.
* @param packet - packet to send.
* @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, 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.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
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 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;
}
}
@@ -17,23 +17,34 @@
package com.comphenix.protocol;
import java.util.Set;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.reflect.FieldAccessException;
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 +63,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 +91,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 +111,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 +123,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.
@@ -112,26 +161,7 @@ public final class Packets {
public static Server getRegistry() {
return INSTANCE;
}
/**
* Determine if the given packet is a valid server packet in the current version of Minecraft.
* @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.
*/
public static boolean isSupported(int packetID) throws FieldAccessException {
return PacketFilterManager.getServerPackets().contains(packetID);
}
/**
* Retrieve every client packet the current version of Minecraft is aware of.
* @return Every supported server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
public static Set<Integer> getSupported() throws FieldAccessException {
return PacketFilterManager.getServerPackets();
}
// We only allow a single instance of this class
private Server() {
super();
@@ -140,8 +170,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 +186,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 +202,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 +218,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.
@@ -185,26 +235,7 @@ public final class Packets {
public static Client getRegistry() {
return INSTANCE;
}
/**
* Determine if the given packet is a valid client packet in the current version of Minecraft.
* @param packetID - the packet to test.
* @return TRUE if this packet is supported, FALSE otherwise.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public static boolean isSupported(int packetID) throws FieldAccessException {
return PacketFilterManager.getClientPackets().contains(packetID);
}
/**
* Retrieve every client packet the current version of Minecraft is aware of.
* @return Every supported client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public static Set<Integer> getSupported() throws FieldAccessException {
return PacketFilterManager.getClientPackets();
}
// Like above
private Client() {
super();
@@ -213,25 +244,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 +283,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,504 @@
/**
* 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 java.util.Locale;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.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);
if (updater.getValues(true).isEmpty()) {
plugin.getLogger().warning("Updater section is missing, regenerate your config!");
}
}
// 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, false);
}
/**
* 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(Locale.ENGLISH).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;
}
}
@@ -0,0 +1,143 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2016 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;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang.Validate;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
/**
* The main entry point for ProtocolLib.
* @author dmulloy2
*/
public class ProtocolLibrary {
/**
* The minimum version ProtocolLib has been tested with.
*/
public static final String MINIMUM_MINECRAFT_VERSION = "1.8";
/**
* The maximum version ProtocolLib has been tested with.
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "1.11.1";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.11) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2016-12-20";
/**
* Plugins that are currently incompatible with ProtocolLib.
*/
public static final List<String> INCOMPATIBLE = Arrays.asList("TagAPI");
private static Plugin plugin;
private static ProtocolConfig config;
private static ProtocolManager manager;
private static ErrorReporter reporter = new BasicErrorReporter();
private static ListeningScheduledExecutorService executorAsync;
private static ListeningScheduledExecutorService executorSync;
private static boolean updatesDisabled;
private static boolean initialized;
protected static void init(Plugin plugin, ProtocolConfig config, ProtocolManager manager, ErrorReporter reporter,
ListeningScheduledExecutorService executorAsync, ListeningScheduledExecutorService executorSync) {
Validate.isTrue(!initialized, "ProtocolLib has already been initialized.");
ProtocolLibrary.plugin = plugin;
ProtocolLibrary.config = config;
ProtocolLibrary.manager = manager;
ProtocolLibrary.reporter = reporter;
ProtocolLibrary.executorAsync = executorAsync;
ProtocolLibrary.executorSync = executorSync;
initialized = true;
}
/**
* Gets the ProtocolLib plugin instance.
* @return The plugin instance
*/
public static Plugin getPlugin() {
return plugin;
}
/**
* Gets ProtocolLib's configuration
* @return The config
*/
public static ProtocolConfig getConfig() {
return config;
}
/**
* Retrieves the packet protocol manager.
* @return Packet protocol manager
*/
public static ProtocolManager getProtocolManager() {
return manager;
}
/**
* Retrieve the current error reporter.
* @return Current error reporter.
*/
public static ErrorReporter getErrorReporter() {
return reporter;
}
/**
* 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;
}
/**
* Disables the ProtocolLib update checker.
*/
public static void disableUpdates() {
updatesDisabled = true;
}
/**
* Whether or not updates are currently disabled.
* @return True if it is, false if not
*/
public static boolean updatesDisabled() {
return updatesDisabled;
}
}
@@ -0,0 +1,76 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2016 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;
import java.text.MessageFormat;
import java.util.logging.Level;
import org.bukkit.plugin.Plugin;
/**
* @author dmulloy2
*/
public class ProtocolLogger {
private static Plugin plugin;
protected static void init(Plugin plugin) {
ProtocolLogger.plugin = plugin;
}
private static boolean isDebugEnabled() {
try {
return plugin.getConfig().getBoolean("global.debug", false);
} catch (Throwable ex) { // Maybe we're testing or something
return false;
}
}
/**
* Logs a message to console with a given level.
* @param level Logging level
* @param message Message to log
* @param args Arguments to format in
*/
public static void log(Level level, String message, Object... args) {
plugin.getLogger().log(level, MessageFormat.format(message, args));
}
/**
* Logs a method to console with the INFO level.
* @param message Message to log
* @param args Arguments to format in
*/
public static void log(String message, Object... args) {
log(Level.INFO, message, args);
}
/**
* Logs a message to console with a given level and exception.
* @param level Logging level
* @param message Message to log
* @param ex Exception to log
*/
public static void log(Level level, String message, Throwable ex) {
plugin.getLogger().log(level, message, ex);
}
public static void debug(String message, Object... args) {
if (isDebugEnabled()) {
log("[Debug] " + message, args);
}
}
}
@@ -0,0 +1,285 @@
/*
* 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.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.ListeningWhitelist;
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;
/**
* Represents an API for accessing the Minecraft protocol.
* @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.
*/
public ImmutableSet<PacketListener> getPacketListeners();
/**
* 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
* can register it again.
* @param listener - new packet listener.
*/
public void addPacketListener(PacketListener 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.
*/
public void removePacketListener(PacketListener listener);
/**
* Removes every listener associated with the given plugin.
* @param plugin - the plugin to unload.
*/
public void removePacketListeners(Plugin plugin);
/**
* 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
* 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);
/**
* 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
* 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 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();
/**
* 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 asynchronous packet manager.
* @return Asynchronous packet manager.
*/
public AsynchronousManager getAsynchronousManager();
public void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist);
}
@@ -0,0 +1,493 @@
/*
* 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.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
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.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 PacketProcessingQueue clientProcessingQueue;
// Sending queues
private final PlayerSendingHandler playerSendingHandler;
// Report exceptions
private final ErrorReporter reporter;
// The likely main thread
private final Thread mainThread;
// Default scheduler
private final BukkitScheduler scheduler;
// Current packet index
private final AtomicInteger currentSendingIndex = new AtomicInteger();
// Our protocol manager
private ProtocolManager manager;
/**
* 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.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 receive these asynchronous events.
* @param autoInject - whether or not to automatically create the corresponding synchronous listener,
* @return An asynchronous handler.
*/
public AsyncListenerHandler registerAsyncHandler(PacketListener listener, boolean autoInject) {
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
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)) {
manager.verifyWhitelist(listener, sendingWhitelist);
serverProcessingQueue.addListener(handler, sendingWhitelist);
}
if (hasValidWhitelist(receivingWhitelist)) {
manager.verifyWhitelist(listener, receivingWhitelist);
clientProcessingQueue.addListener(handler, receivingWhitelist);
}
// We need a synchronized listener to get the ball rolling
if (autoInject) {
handler.setNullPacketListener(new NullPacketListener(listener));
manager.addPacketListener(handler.getNullPacketListener());
}
return handler;
}
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
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
public void unregisterAsyncHandler(AsyncListenerHandler handler) {
if (handler == null)
throw new IllegalArgumentException("listenerToken cannot be NULL");
handler.cancel();
}
// Called by AsyncListenerHandler
void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) {
PacketListener listener = handler.getAsyncListener();
boolean synchronusOK = onMainThread();
// Unregister null packet listeners
if (handler.getNullPacketListener() != null) {
manager.removePacketListener(handler.getNullPacketListener());
}
// Just remove it from the queue(s)
if (hasValidWhitelist(listener.getSendingWhitelist())) {
List<PacketType> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
// We're already taking care of this, so don't do anything
playerSendingHandler.sendServerPackets(removed, synchronusOK);
}
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
List<PacketType> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
playerSendingHandler.sendClientPackets(removed, synchronusOK);
}
}
/**
* Determine if we're running on the main thread.
* @return TRUE if we are, FALSE otherwise.
*/
private boolean onMainThread() {
return Thread.currentThread().getId() == mainThread.getId();
}
@Override
public void unregisterAsyncHandlers(Plugin plugin) {
unregisterAsyncHandlers(serverProcessingQueue, plugin);
unregisterAsyncHandlers(clientProcessingQueue, plugin);
}
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
// Iterate through every packet listener
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
// Remove the listener
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
unregisterAsyncHandler(listener.getListener());
}
}
}
/**
* 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());
// 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();
}
/**
* Retrieve the current task scheduler.
* @return Current task scheduler.
*/
public BukkitScheduler getScheduler() {
return scheduler;
}
@Override
public boolean hasAsynchronousListeners(PacketEvent packet) {
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketType());
return list != null && list.size() > 0;
}
/**
* Construct a asynchronous marker with all the default values.
* @return Asynchronous marker.
*/
public AsyncMarker createAsyncMarker() {
return createAsyncMarker(AsyncMarker.DEFAULT_TIMEOUT_DELTA);
}
/**
* Construct an async marker with the given sending priority delta and timeout delta.
* @param timeoutDelta - how long (in ms) until the packet expire.
* @return An async marker.
*/
public AsyncMarker createAsyncMarker(long timeoutDelta) {
return createAsyncMarker(timeoutDelta, currentSendingIndex.incrementAndGet());
}
// Helper method
private AsyncMarker createAsyncMarker(long timeoutDelta, long sendingIndex) {
return new AsyncMarker(manager, sendingIndex, System.currentTimeMillis(), timeoutDelta);
}
@Override
public PacketStream getPacketStream() {
return manager;
}
@Override
public ErrorReporter getErrorReporter() {
return reporter;
}
@Override
public void cleanupAll() {
serverProcessingQueue.cleanupAll();
playerSendingHandler.cleanupAll();
timeoutListeners.clear();
serverTimeoutListeners = null;
clientTimeoutListeners = null;
}
@Override
public void signalPacketTransmission(PacketEvent packet) {
signalPacketTransmission(packet, onMainThread());
}
/**
* Signal that a packet is ready to be transmitted.
* @param packet - packet to signal.
* @param onMainThread - whether or not this method was run by the main thread.
*/
private void signalPacketTransmission(PacketEvent packet, boolean onMainThread) {
AsyncMarker marker = packet.getAsyncMarker();
if (marker == null)
throw new IllegalArgumentException(
"A sync packet cannot be transmitted by the asynchronous manager.");
if (!marker.isQueued())
throw new IllegalArgumentException(
"A packet must have been queued before it can be transmitted.");
// Only send if the packet is ready
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);
}
}
/**
* 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 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;
}
/**
* Signal that a packet has finished processing.
* @param packet - packet to signal.
*/
public void signalFreeProcessingSlot(PacketEvent packet) {
getProcessingQueue(packet).signalProcessingDone();
}
/**
* 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
*/
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);
}
/**
* Clean up after a given player has logged out.
* @param player - the player that has just logged out.
*/
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,24 +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 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.
@@ -49,6 +60,11 @@ 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();
@@ -75,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)
@@ -87,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;
}
@@ -146,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() {
@@ -223,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();
@@ -251,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);
@@ -271,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);
@@ -279,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>
@@ -288,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())
);
@@ -305,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;
}
}
/**
@@ -334,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.
@@ -386,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.");
@@ -403,58 +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.getErrorReporter().reportMinimal(listener.getPlugin(), "onAsyncPacket()", 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) {
@@ -462,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.ProtocolLogger;
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,36 @@ 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 {
ProtocolLogger.log(Level.WARNING, "Failed to determine contents of incoming chat packet!");
alwaysSync = true;
}
} else if (event.getPacketType() == PacketType.Status.Server.SERVER_INFO) {
return true;
} else {
// TODO: Find more cases of async packets
return false;
}
} else {
System.err.println("Cannot determine asynchronous state of packets!");
ProtocolLogger.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 +468,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(
"[ProtocolLib] 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;
}
}
@@ -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,494 @@
/*
* 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.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 Range.closed(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,618 @@
/*
* 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.ProtocolLogger;
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 (ThreadDeath | OutOfMemoryError e) {
throw e;
} catch (Throwable ex) {
// Don't use the error logger to log errors in error logging (that could lead to infinite loops)
ProtocolLogger.log(Level.WARNING, "Cannot convert to a String with Apache: " + ex.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());
}
}
@@ -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.events;
import com.comphenix.protocol.PacketType.Sender;
/**
* Used to set a packet filter.
*
* @author Kristian
*/
public enum ConnectionSide {
/**
* Listen for server side packets that will invoke onPacketSending().
*/
SERVER_SIDE,
/**
* Listen for client side packets that will invoke onPacketReceiving().
*/
CLIENT_SIDE,
/**
* Listen for both client and server side packets.
*/
BOTH;
public boolean isForClient() {
return this == CLIENT_SIDE || this == BOTH;
}
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,32 @@
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);
@@ -0,0 +1,484 @@
/*
* 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.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
*/
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 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.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.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.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 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.
*/
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() {
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.
*/
public GamePhase getGamePhase() {
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, 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.
* @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) {
if (whitelist != null) {
for (int i = 0; i < idList.length; i++) {
if (whitelist.getWhitelist().contains(idList[i]))
return true;
}
}
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", 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;
}
}
@@ -0,0 +1,636 @@
/*
* 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.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.
* <p>
* Remember to override onPacketReceiving() and onPacketSending(), depending on the ConnectionSide.
* @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]));
}
/**
* 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 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]));
}
/**
* 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, 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 options - which listener options to use.
* @param packets - the packet IDs the listener is looking for.
*/
@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>
* 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)
throw new IllegalArgumentException("connectionSide cannot be null");
if (listenerPriority == null)
throw new IllegalArgumentException("listenerPriority cannot be null");
if (gamePhase == null)
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 = ListeningWhitelist.newBuilder().
priority(listenerPriority).
types(packets).
gamePhase(gamePhase).
options(serverOptions).
build();
if (connectionSide.isForClient())
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
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
}
@Override
public void onPacketSending(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
/**
* 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) {
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 {
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),
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
@@ -0,0 +1,456 @@
/*
* 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.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import java.util.EventObject;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.PluginContext;
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 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.
* @param source - the event source.
*/
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.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.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;
}
/**
* Creates an event representing a client packet transmission.
* @param source - the event source.
* @param packet - the packet.
* @param client - the client that sent the packet.
* @return The event.
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
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.
* @param packet - the packet.
* @param recipient - the client that will receieve the packet.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
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.
* @param marker - the asynchronous marker.
* @return The new packet event.
*/
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
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 !Bukkit.isPrimaryThread();
}
/**
* Retrieves the packet that will be sent to the player.
* @return Packet to send to the player.
*/
public PacketContainer getPacket() {
return packet;
}
/**
* Replace the packet that will be sent to the player.
* @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(PluginContext.getPluginCaller(new Exception()), 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;
}
/**
* 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;
}
/**
* Retrieves the player that has sent the packet or is recieving it.
* @return The player associated with this event.
*/
public Player getPlayer() {
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;
}
/**
* Whether or not this packet was created by the server.
* <p>
* Most listeners can deduce this by noting which listener method was invoked.
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
*/
public boolean isServerPacket() {
return serverPacket;
}
/**
* Retrieve the asynchronous marker.
* <p>
* If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
* <p>
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
* @return The current asynchronous marker, or NULL.
*/
public AsyncMarker getAsyncMarker() {
return asyncMarker;
}
/**
* 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.
* @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.
*/
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
output.defaultWriteObject();
// Write the name of the player (or NULL if it's not set)
output.writeObject(playerReference.get() != null ? new SerializedOfflinePlayer(playerReference.get()) : null);
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
input.defaultReadObject();
final SerializedOfflinePlayer serialized = (SerializedOfflinePlayer) input.readObject();
// 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;
}
}
Loaded 100 of 368 files, more files were not shown because too many files have changed in this diff. Show more