Compare commits

...
1375 Commits
Author SHA1 Message Date
Dan Mulloy e2f949c3a7 Update version to 4.5.0 for release 2019-12-24 15:00:28 -05:00
Dan Mulloy c3c59337ff Update to 1.15.1 2019-12-24 14:57:15 -05:00
Dan Mulloy fdf315ba7d Update URLs to HTTPS
Finally got around to it on my ci and nexus
2019-12-24 14:23:05 -05:00
Dan Mulloy f46bd56e8f Fix name of deprecated BED packet
Might fix compatibility with 1.8
2019-12-17 19:42:31 -05:00
Dan Mulloy b1efed0c0c Fix get entity by ID with Citizens and HD
Fixes #729
Addresses filoghost/HolographicDisplays#135
2019-12-17 19:41:53 -05:00
MiniDigger 524fb1ba68 Support multiple EntityTrackerEntryImpls, fixes #732 (#733) 2019-12-16 12:32:26 -05:00
Dan Mulloy 3ff2ccf1b3 Add build number back to version, update maven plugins 2019-12-15 14:50:39 -05:00
Dan Mulloy e915fd0f9a Fix player action enum compatibility with 1.14 and below 2019-12-15 14:50:02 -05:00
Dan Mulloy 6f8b2377b1 Fix last wrapped exception
Fixes #711
2019-12-14 16:54:18 -05:00
Dan Mulloy c893a3f11e Fix player action enum in 1.15 2019-12-13 18:57:21 -05:00
Dan Mulloy 73c71e0198 Update to Minecraft 1.15 2019-12-13 16:19:40 -05:00
Dan Mulloy bb305fdaad Remove legacy packet id 2019-12-11 21:37:21 -05:00
Dan Mulloy b4d4eb29af Fix error filtering reports
Fixes #718
2019-12-05 17:02:16 -05:00
Dan Mulloy 6d9fe45fb4 Tested with 1.14.4 2019-12-05 16:49:22 -05:00
Dan Mulloy b292c8485b Disable strict JavaDoc checking 2019-12-05 16:48:56 -05:00
Dan Mulloy 1e4da2c93b Update issue templates 2019-11-13 12:12:03 -05:00
Dan Mulloy 84e31d032b Fix villager data breaking versions below 1.14 2019-10-30 12:02:15 -04:00
Dan Mulloy 59c8c8c9db Fix WrappedBlockData deep clone
Fixes #661
Fixes #662
2019-10-29 17:01:15 -04:00
Dan Mulloy a76ceb94cc Add modifier for EntityTypes
Fixes #710
2019-10-29 16:52:30 -04:00
Dan Mulloy 9a108af219 Update dependency info in readme 2019-10-24 09:57:58 -04:00
Dan Mulloy 2f09dec1c5 Move back to Maven
Didn't really gain anything from Gradle but it broke some stuff
2019-10-23 19:17:37 -04:00
Felix Klauke 4c43f125f9 Use gradle wrapper instead of travis ci's inbuilt gradle version. (#706)
At the moment travis will always use its pre installed gradle version what leads to unpredictable build behaviour as the version can change at any time. As this project has already defined a gradle wrapper the CI should use it.
2019-10-16 12:07:07 -04:00
Dan Mulloy b5cff49358 Update issue templates 2019-10-01 17:28:36 -04:00
Dan Mulloy 446a1c8326 Clone OptionalInts too 2019-08-07 11:38:50 -04:00
Dan Mulloy 8c20d19339 Try to fix a null pointer
Addresses #665
2019-08-07 11:34:59 -04:00
Dan Mulloy 9dfffe5366 Move Travis to openjdk8
Something about Oracle and licenses. Who woulda thought
2019-08-03 13:10:20 -04:00
Dan Mulloy 19dd81ce94 Fix EntityUtilities updateEntity on 1.14
Fixes #651
2019-08-03 13:03:16 -04:00
Dan Mulloy dd9eac3d6d Add wrapper for villager data
Fixes #637
2019-08-03 12:35:58 -04:00
Dan Mulloy 916434251d Add support for long array nbt type
Fixes #648
2019-08-03 11:46:58 -04:00
Dan Mulloy 62e8d82e16 Update to 1.14.4, add BLOCK_BREAK packet
Also called Acknowledge Player Digging, but we go by what Spigot calls it
2019-08-03 11:41:55 -04:00
BuildTools d7be712959 Add api-version, tweak version pattern to support pre releases
Fixes #638
Fixes #636
Fixes #626
2019-06-23 15:28:40 +02:00
Dan Mulloy 490b1dadcb Fix logger debug when testing 2019-05-07 23:11:05 -04:00
Dan Mulloy 502c5960e3 Fix join/quit exceptions when wrappers break 2019-05-07 22:58:59 -04:00
Dan Mulloy d297e373b4 Specifically clone Spigot's chat components to fix stack overflow
Addresses #601
2019-05-06 20:32:20 -04:00
Dan Mulloy 9f5d95f617 Hopefully fix chat cloning issues
Addresses #601
2019-05-06 16:20:19 -04:00
Dan Mulloy b14b4cc345 Fix entity tracking in 1.14
Fixes #600
2019-05-04 22:41:32 -04:00
Dan Mulloy e41bb8753b Backport some fixes, fix version in plugin yml
Still need to get automatic filtering fixed
2019-05-04 01:45:41 -04:00
Dan Mulloy 565f169e94 Update to Minecraft 1.14 2019-05-03 23:22:44 -04:00
Dan Mulloy 7b7449ee15 Migrate to Gradle with a single module 2018-09-22 17:48:06 -04:00
Dan Mulloy 95bb026fa9 Update parent pom version
Can't wait to move to gradle with just one build file
2018-09-22 17:27:53 -04:00
Dan Mulloy 4713d320f8 Update version to 4.4.0 for release 2018-09-22 12:21:17 -04:00
Dan Mulloy 083c8e1481 Mark ParticleTypes as immutable, update tested version 2018-09-22 12:17:02 -04:00
Dan Mulloy 6ff6976e85 Merge branch 'master' of https://github.com/aadnk/ProtocolLib 2018-09-18 17:04:10 -04:00
Dan Mulloy 7e87c90b8d Add method to create wrapped block data from the spigot api
Fixes #525
2018-09-18 17:02:19 -04:00
Dan Mulloy 6fdf0f8255 Add modifier for dimensions 2018-09-18 17:01:55 -04:00
Dan Mulloy 7e12f57eda Update testing nms package 2018-09-15 14:48:40 -04:00
Dan Mulloy 776ec56a2d Update to 1.13.1, rework cloning, fix a particle NPE 2018-09-15 14:32:18 -04:00
Dan Mulloy 8ec31d83db Fix #508 2018-08-24 10:44:37 -04:00
diffblue-assistant 1373911ca2 Use try-with-resources (#158)
Replace try statements that close a resource in the finally block with a
try-with-resources statement [1].

This commit was created automatically by Diffblue refactorings (https://www.diffblue.com/).

[1] https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
2018-08-23 12:51:21 -04:00
Dan Mulloy cf5d9dfc7d Account for new network manager send packet method
Fixes #497
2018-08-03 22:18:32 -04:00
Dan Mulloy 9df0dd48e4 Fix backwards compat with nullable classes
Fixes #499
2018-08-03 22:07:25 -04:00
Dan Mulloy d112e9b1dc Remove extraneous debug
#494
2018-08-03 21:58:54 -04:00
Dan Mulloy 206dc30e17 How did those get there 2018-08-01 17:44:25 -04:00
Dan Mulloy 4c3a0afd7e Shiny new 1.13 particle support
Hopefully this doesn't break 1.8-1.12
2018-08-01 17:38:56 -04:00
Dan Mulloy 8d9bf7d91f Clone Java optionals
Fixes #498
2018-08-01 16:08:53 -04:00
Dan Mulloy 3ef537242f Fix some issues with item equality
Seems to be an internal Bukkit bug, hopefully nothing more serious
2018-07-24 17:05:30 -04:00
Dan Mulloy 976e4b4217 Update to 1.13 2018-07-23 19:11:07 -04:00
Dan Mulloy e827a3dd1f Fix class not found exception on versions < 1.13 2018-07-22 00:49:03 -04:00
Dan Mulloy 778031c105 Change block converter to use reflection again 2018-07-19 13:29:20 -04:00
Dan Mulloy 6c3654ef40 Update to 1.13-pre7 2018-07-19 13:13:06 -04:00
Dan Mulloy 5048f6f34b Try a less expensive map impl for packet markers
This might be a concurrency nightmare or a nice solution. I'm hopeful
based on initial testing
Addresses #489
2018-07-09 22:32:21 -04:00
Dan Mulloy c9dd6e2ba7 Merge remote-tracking branch 'origin/master' 2018-07-09 22:31:23 -04:00
Dan Mulloy fc76af9d56 Use broader catch statement when dealing with other metrics plugins
Fixes #488
2018-07-09 22:31:03 -04:00
dextonanderson aed2285bcb Properly cleanup ByteBuf in WirePacket (#487)
* Properly cleanup ByteBuf

* Release store ByteBuf also
2018-07-09 12:07:43 -04:00
Alvin-LB 1d11908af1 Make sure getMinecraftItemStack(ItemStack) handles Material.AIR properly (#485)
Fixes #483 and adds a test case to make sure the issue doesn't occur again.
2018-06-29 12:07:48 -04:00
Dan Mulloy 3ccc56fef9 Update repository links and versions 2018-06-12 14:45:36 -04:00
Dan Mulloy 39e0c974ee Merge branch 'master' of https://github.com/aadnk/ProtocolLib 2018-06-04 12:59:28 -04:00
Dan Mulloy e52c69ca22 Merge pull request #152 from MatrixTunnel/master
Fix maven/gradle repo url
2018-06-04 12:58:52 -04:00
MatrixTunnel d7d53bd905 Fix maven/gradle repo url 2018-06-02 21:26:31 -07:00
Dan Mulloy fb2d5016e5 Fix index out of bounds exception
Also make /all/ the IntelliJ code changes

Fixes #469
2018-05-31 20:15:47 -04:00
Dan Mulloy 015099b92e Clone NBT tags
Fixes #366
2018-05-29 10:53:07 -04:00
Dan Mulloy 7e8356f0bb Remove zip functionality from the updater
I don't upload zip files anyways
2018-05-29 10:41:13 -04:00
Dan Mulloy 72b8c4ced8 👀 2018-05-28 23:21:03 -04:00
Dan Mulloy 2d22df404a Fix Gradle builds 2018-05-28 23:19:56 -04:00
MiniDigger 0f13a32925 add paper to the "isUsingSpigot" check (#468)
partially fixes #467
will make protocollib use the spigot updater on paper servers.
2018-05-18 14:10:46 -04:00
Dan Mulloy 1ddb7a0014 Fix a null pointer with the Bukkit updater 2018-04-14 13:53:54 -04:00
Dan Mulloy f9376432c4 Metrics improvements 2017-10-08 16:02:10 -04:00
Dan Mulloy d195fb6990 Update version constants to 1.12.2 2017-10-08 15:56:13 -04:00
Dan Mulloy e4be102a77 Update to 1.12.2 2017-09-27 19:44:34 -04:00
Dan Mulloy 5f14ccdc34 Add back EnumConverters#getGenericConverter(Class)
It should work, but is deprecated and not recommended
Addresses #394
2017-09-27 19:43:38 -04:00
Mark Hughes 7573459a48 Update build information (#409)
- Switch to 4.3.0 version
- Include Gradle information
2017-09-27 19:19:42 -04:00
Dan Mulloy 01b68cf8e5 Revert unnecessarily breaking changes to converters
Fixes #394
2017-08-30 21:51:21 -04:00
Gabriele C f3b1a07189 Replace MCstats with bStats (#389)
#388
2017-08-19 11:25:54 -04:00
Dan Mulloy c70eb27653 Update ISSUE_TEMPLATE.md 2017-08-19 11:12:21 -04:00
Dan Mulloy 4483ce0d79 Add NbtFactory#fromItemOptional
This new method won't create tags if they don't already exists

Fixes #376
2017-08-13 23:00:51 -04:00
Dan Mulloy 1b23e9ec22 Rewrite the packet metadata API based on the NMS packet
Meta, if created, will persist between creation and listening.
2017-08-13 22:47:45 -04:00
Dan Mulloy 1f5692a0c7 Merge remote-tracking branch 'origin/master' 2017-08-04 14:01:24 -04:00
Dan Mulloy 0ac68dc179 Update to 1.12.1
Also contains changes for the packet wrapper update coming soonTM
2017-08-04 14:01:06 -04:00
dadus33 d891a057c0 Make #deserializeLegacy() able to access private members (#386) 2017-07-30 22:31:18 -04:00
Dan Mulloy 9e5bdf4124 Major rework of underlying conversion logic
This shouldn't affect plugins utlilizing the API, but will make updates and adding wrappers to PacketWrapper easier. In case you're wondering, major changes include:

- Added AutoWrapper
- Added Covnerters utility class
- Removed generic type parameter from EquivalentConverter
2017-07-24 14:15:56 -04:00
Dan Mulloy e1ea295600 Fix one of many issues with reloading
I'm only fixing this because it was relatively simple and I got a lot of reports for it. Seriously people, Stop. Reloading.

Fixes #373, fixes #368, fixes #360, fixes #359
2017-07-08 02:10:24 -04:00
蒋天蓓 59171c5443 Fix issue with component array converter (#372) 2017-07-07 11:42:17 -04:00
Dan Mulloy 501244cb3c Attempt to fix Jenkins build
Oddly enough it builds with no errors on my computer
2017-07-06 14:32:35 -04:00
Dan Mulloy 390c21f6d5 Add PacketEvent#isPlayerTemporary, check for player updates
This should address issues with temporary players by hopefully returning them less often
2017-07-06 13:56:30 -04:00
Dan Mulloy 20d78832b0 Update dependency versions to hopefully work with Java 9 2017-06-14 15:41:00 -04:00
Dan Mulloy adb3c5392c Release version 4.3.0 2017-06-14 14:30:46 -04:00
Dan Mulloy cd2f56d7c7 Update TinyProtocol to 1.12 2017-06-14 14:30:31 -04:00
Dan Mulloy 7c9bbc3f6c Add 1.12 protocol version 2017-06-09 15:08:04 -04:00
Dan Mulloy 52e396b25d Update to Spigot 1.12 2017-06-08 13:30:57 -04:00
Dan Mulloy aeb2cfb65e Update to 1.12-pre6
No real changes this time
2017-05-30 14:31:13 -04:00
Dan Mulloy 7f5ea6e493 Merge remote-tracking branch 'origin/master' 2017-05-29 22:14:28 -04:00
Dan Mulloy 1be94aad78 Rework packet type deprecation to actually work properly
Also fix compatibility with 1.8.0
2017-05-29 22:14:08 -04:00
dadus33 6bb169ac01 Fix 1.8 compatibility (#339)
Use proper method name ('setLenient' instead of 'setLenienent') in the deserializeLegacy method for 1.8 compatibility.
2017-05-29 12:59:01 -04:00
Dan Mulloy ea7900d64f Better ensure packet types are correct, deprecate legacy IDs 2017-05-24 17:20:27 -04:00
Dan Mulloy 123abdff30 Code cleanup, remove some ancient compatibility 2017-05-24 14:35:22 -04:00
Dan Mulloy 11247f1c01 Return an empty modifier if a class does not exist
Addresses #336
2017-05-23 18:52:28 -04:00
Dan Mulloy f093c9196d Ignore IntelliJ files 2017-05-19 11:17:31 -04:00
Dan Mulloy 81daa62071 Update to 1.12-pre5 2017-05-19 11:11:30 -04:00
Dan Mulloy f99b977888 Restore backwards compatibility, restructure poms 2017-05-18 12:02:47 -04:00
Dan Mulloy b00ae2ff15 Remove Java 8 warning
It's required, so the JVM will warn you for me
2017-05-14 21:46:18 -04:00
Dan Mulloy b4f9c501b3 1.12 hotfix: delay injection for network manager
Fixes #332, fixes #330
2017-05-14 21:37:36 -04:00
Dan Mulloy 1738c6f243 Stop testing with Java 7 2017-05-14 14:37:55 -04:00
Dan Mulloy abea7441a4 Merge branch 'master' of git+ssh://git@github.com/dmulloy2/ProtocolLib 2017-05-14 14:30:21 -04:00
Dan Mulloy 3887ba27d2 Update to the 1.12 pre-release
- Now requires Java 8
- Added new packets
- Added ChatType enum (replaces magic value)
- Missing protocol version
2017-05-14 14:28:56 -04:00
games647 76a3af84a1 Strictly match the sendPacket method
Related to #322
2017-04-27 20:49:37 -04:00
Dan Mulloy be5d42ee19 Minecraft 1.12 will use Java 8 2017-04-27 20:48:02 -04:00
Dan Mulloy 790711c16e Update version to 4.2.2-SNAPSHOT 2017-04-27 20:46:04 -04:00
Dan Mulloy 8c3127dee1 Release version 4.2.1 2017-04-09 11:56:53 -04:00
Dan Mulloy 5ec87c9d76 Fix issues with logging custom payload packets 2017-03-20 21:42:46 -04:00
Dan Mulloy 2205ce639e Merge pull request #305 from aadnk/master
Branch hiccup
2017-03-18 19:33:14 -04:00
Dan Mulloy 2844d75607 Merge pull request #127 from dmulloy2/master
Branch hiccup
2017-03-18 19:32:12 -04:00
Dan Mulloy a6810ea874 Merge branch 'master' of git+ssh://git@github.com/dmulloy2/ProtocolLib 2017-03-18 19:29:43 -04:00
Dan Mulloy aaf1af8e41 Don't worry if we can't determine the Java version 2017-03-18 19:29:16 -04:00
Dan Mulloy b8dd43aeed Merge pull request #126 from joeypoulissen/master
Added missing fallingdust particle as seen at http://wiki.vg/Protocol#Particle
2017-03-12 18:42:29 -04:00
Joey e959efc5af Added fallingdust particle as seen at http://wiki.vg/Protocol#Particle 2017-03-11 13:59:25 +01:00
Dan Mulloy 73ce01bbde Make packet logging more robust 2017-03-09 10:53:06 -05:00
Dan Mulloy b30628f6db Make hex dump more readable, match more packet names 2017-03-08 09:08:29 -05:00
Dan Mulloy 9289825d87 Add packet logging for debugging 2017-03-01 17:02:46 -05:00
Dan Mulloy 57cfbc4f81 Merge branch 'master' of git+ssh://git@github.com/dmulloy2/ProtocolLib 2017-02-04 12:12:54 -05:00
Dan Mulloy 3ab2026d62 People don't care if updater sites are down 2017-02-04 12:12:31 -05:00
Dan Mulloy e439c778a6 Use the correct JsonReader constructor in 1.8
Fixes aadnk#121
2017-01-01 16:13:47 -05:00
Dan Mulloy 98413c917e Update packet IDs 2016-12-22 12:24:18 -05:00
Dan Mulloy 540a5e53c4 Make Sound handling more robust
Fixes aadnk#119
2016-12-22 11:42:52 -05:00
Dan Mulloy 27047f83a5 Fix erroneous startup message 2016-12-22 11:33:55 -05:00
Dan Mulloy 222b535c15 Update version to 4.2.1-SNAPSHOT 2016-12-22 11:33:10 -05:00
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 0dc2bfef0c Handle exceptions in injected code.
This is very important, otherwise the server may crash unnecessarily.
2012-10-29 17:20:04 +01:00
Kristian S. Stangeland f9c0f212c1 Put UnsupportedListener at the package level. 2012-10-29 16:57:07 +01:00
Kristian S. Stangeland cbf4def71b Prevented map chunk listeners from crashing the server.
If the NetServerHandler injector fails and we revert to the network
field injector instead, any map chunk listener will crash the server
due to complications with Bukkit's ChunkCompressionThread. 

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

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

In addition, I've begun adding cleanup code that will reset all static
fields once the plugin has unloaded.
2012-10-29 01:42:22 +01:00
Kristian S. Stangeland eaf0b73c00 Increment version to 1.4.4-SNAPSHOT (skipping release of 1.4.3) 2012-10-28 06:10:31 +01:00
Kristian S. Stangeland cb2c7b4e0a Use a class cache when constructing the packet interceptor. 2012-10-28 06:09:29 +01:00
Kristian S. Stangeland 4e9b5009c8 Don't print messages to the console without a plugin prefix.
It's bad practice.
2012-10-23 00:54:41 +02:00
Kristian S. Stangeland 7536815e58 Set as SNAPSHOT in the plugin-file as well. 2012-10-23 00:41:53 +02:00
Kristian S. Stangeland 96ad954cf7 Prevent the PlayerQuitEvent from being fired twice. FIXES Ticket-2. 2012-10-23 00:37:35 +02:00
Kristian S. Stangeland 0aac8ebf0a Increment to 1.4.3 snapshot. 2012-10-21 22:40:26 +02:00
Kristian S. Stangeland a60dc3d778 Make the client packet reader thread-safe. 2012-10-21 22:39:56 +02:00
Kristian S. Stangeland 01c481dc93 Release of 1.4.2 2012-10-21 20:42:29 +02:00
Kristian S. Stangeland a51ad1f50f Properly remove previous hook, even if socket is NULL. FIXES Ticket-1. 2012-10-21 20:31:20 +02:00
Kristian S. Stangeland c08106a923 Increment to 1.4.2 snapshot. 2012-10-21 16:33:52 +02:00
Kristian S. Stangeland 63c468eff0 Packet constructor can now handle primitive parameter types. 2012-10-21 16:33:41 +02:00
Kristian S. Stangeland 43c0b5d8f1 Don't save NULL injectors in a ConcurrentHashMap. 2012-10-21 12:11:59 +02:00
Kristian S. Stangeland 331fc94190 Sending or receiving packets now work without listeners. 2012-10-19 16:27:49 +02:00
Kristian S. Stangeland caed0dcb11 Removed the spammy "Entity does not exist" error. 2012-10-19 00:54:03 +02:00
Kristian S. Stangeland 90f2caa5e8 Incremented to 1.4.1 2012-10-17 09:54:28 +02:00
Kristian S. Stangeland 69a5675161 Fixed a bug causing a StackOverflowException on packet construction. 2012-10-17 09:53:59 +02:00
Kristian S. Stangeland f8a8a645eb Added JavaDoc again. What the ... feels like everything is falling
apart.
2012-10-17 08:16:18 +02:00
Kristian S. Stangeland 32ed35d409 Adding GamePhase too. 2012-10-17 08:11:11 +02:00
Kristian S. Stangeland 242ddd5f40 Updated JavaDoc for 1.4.0 2012-10-17 08:07:20 +02:00
Kristian S. Stangeland b9ed36641f Merge branch 'master' into gh-pages
Conflicts:
	ProtocolLib/.classpath
	ProtocolLib/.settings/org.eclipse.core.resources.prefs
2012-10-17 08:01:30 +02:00
Kristian S. Stangeland 0622322ef4 Merged. 2012-10-17 07:58:24 +02:00
Kristian S. Stangeland d5028b584d Put the provided CGLib dependency in the com.comphenix namespace. 2012-10-17 06:57:05 +02:00
Kristian S. Stangeland 2b6a4570ab Added a couple of extra constructors. 2012-10-17 05:58:47 +02:00
Kristian S. Stangeland 4717cca2ec Increment to 1.4.0. 2012-10-17 05:32:46 +02:00
Kristian S. Stangeland dd5303ea88 Use a boolean lookup table instead of a ConcurrentHashMap. 2012-10-17 04:13:25 +02:00
Kristian S. Stangeland fee9a32e4b Store a current player injection hook for each game phase.
That way, one failing the other won't cause any problems.
2012-10-17 03:33:49 +02:00
Kristian S. Stangeland 3a8a4d15cf Fixed a couple of small bugs. 2012-10-17 01:19:06 +02:00
Kristian S. Stangeland 4cd5d04cae Wait 5 seconds before unhooking every player in the server. 2012-10-17 00:46:51 +02:00
Kristian S. Stangeland 476a918794 Dynamically add or remove injected hooks depending on the listeners.
This occurs whenever a listener is added or removed. A listener can
now specify whether or not it's listening for packets sent BEFORE
a player has logged in (every packet upto Packet1Login and a few more),
or AFTER. By default, listeners only receive notifcation of packets
sent and received after. 

ProtocolLib will now only hook NetLoginHandler if there's a login 
listener, and vice versa. Thus, the new login feature will only
tax the server if another plugin is using it. In addition, ProtocolLib
will not consume any resources when it's not serving any listeners.
2012-10-16 22:24:30 +02:00
Kristian S. Stangeland ecdc9b4b6c Let's be generous and clean up the injection before the other plugins. 2012-10-16 17:01:34 +02:00
Kristian S. Stangeland 94efb38324 Retrieve the updated player entity as fast as we can. 2012-10-16 16:42:27 +02:00
Kristian S. Stangeland 09348343dd NetLoginInjector should not be public. 2012-10-16 08:01:04 +02:00
Kristian S. Stangeland d88f507337 Removed debug listener. 2012-10-16 07:59:03 +02:00
Kristian S. Stangeland b52670c4b3 Switching to overriding an ArrayList directly.
This makes the injection code compatible with earlier versions of
Minecraft (even 1.0.0).
2012-10-16 07:58:30 +02:00
Kristian S. Stangeland 93468b53b3 Massive update that adds support for intercepting packets during login.
This is achieved by injecting the NetLoginHandler when it's added
to the DedicatedServerConnectionThread's list of current login handlers.

PacketEvents during this phase uses "fake" Player objects that only
support a subset of methods. Consumers can expect the following methods
to be functional:
 * getPlayer()
 * getAddress()
 * getServer()
 * chat(String)
 * sendMessage(String)
 * sendMessage(String[])
 * kickPlayer(String)

A "fake" Player object can be converted to its real counterpart by
calling getPlayer().
2012-10-16 07:28:54 +02:00
Kristian S. Stangeland 9b4b161602 First test of the pre-login injector. 2012-10-15 00:31:55 +02:00
Kristian S. Stangeland 39805c5502 Incremented version to 1.3.3 2012-10-13 22:19:07 +02:00
Kristian S. Stangeland 98ae5c6e29 Added a monitor every packet listener. 2012-10-13 22:19:01 +02:00
Kristian S. Stangeland a173824b3f Switching to the public repository group. 2012-10-13 18:07:02 +02:00
Kristian S. Stangeland 51c5a23c83 Switching to the "repo" virtual website instead. 2012-10-13 17:19:45 +02:00
Kristian S. Stangeland 61cd8a3ae4 Using a self-hosted repository for now. 2012-10-12 04:09:38 +02:00
Kristian S. Stangeland fe3a69bd3d Switching from UTF-8 to cp1252. 2012-10-12 00:49:47 +02:00
Kristian S. Stangeland 8e70a56768 CGlib must be in the compile scope, otherwise it's not included.
In addition, updated the README with information about Maven.
2012-10-12 00:24:15 +02:00
Kristian S. Stangeland 28fc096740 Bumping to version 1.3.2 2012-10-12 00:04:59 +02:00
Kristian S. Stangeland 129687772f Mark CraftBukkit and cglib as provided.
CraftBukkit, or just Bukkit, will already be a dependecy for any
plugin. CGlib will be provided by ProtocolLib, so that's not needed
either.
2012-10-12 00:04:05 +02:00
Kristian S. Stangeland 768d169f27 Added the same NULL check to the generic Bukkit unwrapper. 2012-10-12 00:01:05 +02:00
Kristian S. Stangeland 57add8e26f Handling the case where someone is writing a NULL element to
a equivalent converter.
2012-10-11 23:57:45 +02:00
Kristian S. Stangeland 8bd7f75a6d Updating POM to use Sonatype Nexus OSS. 2012-10-11 22:30:46 +02:00
Kristian S. Stangeland 3af9196740 Converted ItemDisguise to a maven project. 2012-10-11 09:09:42 +02:00
Kristian S. Stangeland ff0cd06b2e [maven-release-plugin] prepare release ProtocolLib-1.3.1 2012-10-11 08:03:59 +02:00
Kristian S. Stangeland 97a22a0f6d Revert to SNAPSHOT due to buggy maven. 2012-10-11 08:03:45 +02:00
Kristian S. Stangeland 5a8847a719 Updated POM to use my personal maven repo. 2012-10-11 08:00:00 +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 707b4511ce Add repository information to POM. 2012-10-11 02:56:45 +02:00
Kristian S. Stangeland b4d0c4a36d Prepare POM for possibly being on the public maven repository. 2012-10-11 02:17:45 +02:00
Kristian S. Stangeland 8d3c1de9b9 Adding GPL v2 license information to every file. 2012-10-10 22:18:11 +02:00
Kristian S. Stangeland dc75f34e11 Add a warning message for lost packets. 2012-10-10 22:12:29 +02:00
Kristian S. Stangeland 5bda655f39 Switching to Maven instead of Ant. 2012-10-10 21:35:55 +02:00
Kristian S. Stangeland 4301dc6525 Add the ability to customize the worker threads. 2012-10-10 07:16:01 +02:00
Kristian S. Stangeland 17b7526fe9 Set the queued index before enqueuing, not after. 2012-10-10 06:00:42 +02:00
Kristian S. Stangeland cf68d229b0 Honor the sending index when the packet has finished processing. 2012-10-10 05:42:45 +02:00
Kristian S. Stangeland 7ed0bc82dd Make it possible for threads to delay packet transmission.
Threads can now increment a shared counter indicating that a packet
should not be transmitted after the default packet listener 
processing. This can be useful if a packet listener needs information
from additional packets before it can complete.

Packet listeners that whish to use this method begin by calling
incrementPacketDelay(). It is then responsible for calling
signalPacketTransmission() when it's done waiting. All processing
on PacketEvents outside packet listeners must be synchronized
with getProcessingLock().
2012-10-10 04:41:07 +02:00
Kristian S. Stangeland 20792aa09a Reorder injection hooks.
We put the network manager hook to the bottom as it's the least
likely version to be compatible with other plugins.
2012-10-10 02:50:27 +02:00
Kristian S. Stangeland eb328c41e8 Increase the player listener to MONITOR. 2012-10-10 02:48:24 +02:00
Kristian S. Stangeland 6053b9e64b Added the ability to determine if any client or server packet is valid.
Also, added a warning when a plugin attempts to listen for a packet
ID that doesn't exist in the current Minecraft version.
2012-10-10 02:41:34 +02:00
Kristian S. Stangeland f8bd36bf3c Added the ability to enumerate IntEnums. 2012-10-09 22:37:33 +02:00
Kristian S. Stangeland cecab6a169 Unwrap collections in the packet constructor. 2012-10-09 17:41:37 +02:00
Kristian S. Stangeland 2b90acf53e Ensure that non-CraftBukkit item stacks are correctly converted. 2012-10-09 16:48:55 +02:00
Kristian S. Stangeland 5e36547aa2 Give automatically created worker threads sensible names. 2012-10-09 00:07:40 +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 12eab67db7 Fixed JavaDoc. 2012-10-05 04:43:41 +02:00
Kristian S. Stangeland f56cf3290e Merge branch 'master' into gh-pages 2012-10-05 04:40:27 +02:00
Kristian S. Stangeland 88da4e3272 Bumping to version 1.3.0 2012-10-05 03:50:28 +02:00
Kristian S. Stangeland 0e76d8ea2b Make the proxy creation even more flexible.
Now we even support Orebfuscator without using Spout.
2012-10-05 03:12:35 +02:00
Kristian S. Stangeland 18ef06ea21 Adding "support" for Spout by proxying it's NetServerHandler.
While it may seem better to use a Spout PacketListener, we can't
prevent other spout listeners from stopping the listener
chain. For instance, Orebfuscator does supports Spout, but does this
by cancelling every chunk packet and sending them to be processed and
sent by Orebfuscator only. This can never be made compatible with
other plugins.

So, we choose to add some overhead and inject our proxy onto Spout. 
Fortunately, Spout injects the proxy using a player listener on LOWEST, 
so we get to override Spout again with our HIGHEST. The proxy method
should be generic enough to handle most proxy types.
2012-10-05 01:41:17 +02:00
Kristian S. Stangeland af3e278e06 Moved all player related injection methods into a separate package.
This should make the code a little bit clearer.
2012-10-04 21:56:39 +02:00
Kristian S. Stangeland eb12808483 Prevent method #3 from creating uneeded proxy objects.
This would happen if the NetServerHandler is already a proxy, and
the injection method is unable to create an instance of the proxy
type. 

It's best to fail instead of polluting (due to constructors with
side-effects) Minecraft with failed proxy objects.
2012-10-04 17:43:46 +02:00
Kristian S. Stangeland f0651f7170 Don't blow up the fallback method just because a plugin isn't
compatible.
2012-10-04 09:01:33 +02:00
Kristian S. Stangeland debf8c4d88 Create the worker ID before it is run.
In addition, we'll prevent reuse of worker objects.
2012-10-04 06:23:27 +02:00
Kristian S. Stangeland af2d692c59 Wait and notify on the correct lock. 2012-10-04 06:13:19 +02:00
Kristian S. Stangeland db8db1fba1 Make it possible to identify the current worker. 2012-10-04 05:14:17 +02:00
Kristian S. Stangeland e729583d74 Make the manual asynchronous worker closable.
Added a good deal of synchronization to deal with closing a 
specific worker. This overhead can be avoided by simply closing 
any given worker.
2012-10-04 03:20:09 +02:00
Kristian S. Stangeland 8a5e5e849b Attempt to work around existing injected proxies.
This didn't work for Orebfuscator though.
2012-10-04 00:58:05 +02:00
Kristian S. Stangeland 558eab2253 Use an atomic integer instead of a volatile field.
Incrementing and decrementing is not thread-safe on volatile variables.
2012-10-03 23:13:05 +02:00
Kristian S. Stangeland c6fb01e1e1 Adding the ability to use multiple worker threads. 2012-10-03 23:10:35 +02:00
Kristian S. Stangeland eb8ac33a3e Removed uncompleted code. 2012-10-02 23:07:03 +02:00
Kristian S. Stangeland 3f1b5d49e9 Ignore offline players when sending overdue packets. 2012-10-02 23:05:31 +02:00
Kristian S. Stangeland 1e1875cbd8 Speed up the proxy object by using a NoOp filter. 2012-10-02 04:36:47 +02:00
Kristian S. Stangeland 48cedd20d4 Add some size methods. 2012-10-02 04:02:54 +02:00
Kristian S. Stangeland e4e4581717 Bumping to 1.2.2 2012-10-02 03:55:07 +02:00
Kristian S. Stangeland 2fceaa803e Fixed the "moved too quickly" error that would cause players to
fall out into the void on 1.3.2.
2012-10-02 03:54:50 +02:00
Kristian S. Stangeland 0470b2335c Bumping to 1.2.1 2012-10-01 21:47:36 +02:00
Kristian S. Stangeland fe55bb2e56 Processed packets is now sorted by the sending index. 2012-10-01 21:47:19 +02:00
Kristian S. Stangeland 7b35dd954c Switching to the network server object. 2012-10-01 20:30:56 +02:00
Kristian S. Stangeland f4f28023fa Always create the NullPacketListener (unless otherwise stated).
This functions like a crude "counter" that automatically unregisters 
the packet injectors when every asynchronous listener has been removed.
2012-10-01 06:04:31 +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 961b34da38 Update documentation (may still need some work). 2012-10-01 04:59:27 +02:00
Kristian S. Stangeland c2b4b5fce3 Ensure that the compiled structures respect the converter field.
In addition, fixed a bug that prevented client listeners from
receiving any packets.
2012-10-01 02:17:13 +02:00
Kristian S. Stangeland 46d9a6e975 Ensure that the structure compiler is thread safe. 2012-10-01 00:57:14 +02:00
Kristian S. Stangeland e6de9ae705 Remove debug line. 2012-10-01 00:16:34 +02:00
Kristian S. Stangeland 3ad24921d9 Ensure that hook method #3 receieves Packet0KeepAlive. 2012-10-01 00:16:06 +02:00
Kristian S. Stangeland 9770257a74 Bumping to 1.2.0 2012-09-30 04:49:54 +02:00
Kristian S. Stangeland f312ce278e Increase the maximum. 2012-09-30 04:44:57 +02:00
Kristian S. Stangeland 73005e032b Forgot to release a semaphore lock. 2012-09-30 04:42:58 +02:00
Kristian S. Stangeland e666d17dc2 Don't try to find the isAsyncPacket method over and over again. 2012-09-30 04:25:56 +02:00
Kristian S. Stangeland 6063f437fd Another diffcult to track down bug. 2012-09-30 04:24:27 +02:00
Kristian S. Stangeland 1c41f83305 Fixed (probably) a problem reported by Milkywayz.
Relevant crash dump:
2012-09-30 03:39:26 +02:00
Kristian S. Stangeland 801bf81f15 Incredibly difficult bug to track down.
Forgot to remove the "override" flag on cancelled packets when
they're sent again.
2012-09-30 03:24:13 +02:00
Kristian S. Stangeland 0cff9d1243 Ensure that asynchronous listeners are run, even if they have no
accompanying synchronous listener.
2012-09-30 00:51:59 +02:00
Kristian S. Stangeland c77103246e Small bug fixes. 2012-09-30 00:25:04 +02:00
Kristian S. Stangeland 711990fa15 Simplify the act of creating an asynchronous listener. 2012-09-30 00:13:36 +02:00
Kristian S. Stangeland 63197bbbd5 Add the other cases where we need to know if we're on the main thread
or not.
2012-09-29 23:52:28 +02:00
Kristian S. Stangeland 8a26d047b2 Client packets are processed on the server, so they must be
synchronized with the main thread.
2012-09-29 23:48:09 +02:00
Kristian S. Stangeland d58ff1c4c1 Properly clean up after async listeners. 2012-09-29 23:21:09 +02:00
Kristian S. Stangeland 15b33925c0 Fixed a couple of minor bugs. 2012-09-29 22:25:09 +02:00
Kristian S. Stangeland 721d92bd4f Fixed a minor bug. 2012-09-29 22:20:21 +02:00
Kristian S. Stangeland b3098bc6ad Pull up the important methods in the asynchronous packet handler. 2012-09-29 22:18:19 +02:00
Kristian S. Stangeland e86c3d3a6e We have to keep the setAsyncMarker method. 2012-09-29 22:12:30 +02:00
Kristian S. Stangeland 65b5a0e8ec We don't expect consumers to create asynchronous markers, so
don't allow them to set it in the packet event.
2012-09-29 21:42:16 +02:00
Kristian S. Stangeland a6db5419c0 Detect packet timeout. 2012-09-29 21:29:12 +02:00
Kristian S. Stangeland 4f4202185c Make it even clearer that the listener loop method should be
called from a separate thread.
2012-09-29 21:18:21 +02:00
Kristian S. Stangeland 025e97ca95 Renamed the ListenerToken in the code as well. 2012-09-29 20:34:29 +02:00
Kristian S. Stangeland 880520cd94 Renamed the ListenerToken into a AsyncListenerHandler. 2012-09-29 20:32:18 +02:00
Kristian S. Stangeland fad6a0a99c Connect the asynchronous listeners to the system. 2012-09-29 20:30:22 +02:00
Kristian S. Stangeland 81321383d5 Renamed the async marker. Added handling of close. 2012-09-29 19:33:22 +02:00
Kristian S. Stangeland 285952b14d Use the normal packet listener instead of an async listener. 2012-09-29 19:13:12 +02:00
Kristian S. Stangeland 6f02e79802 Still a work in progress.
I'm considering removing the async listener and use the packet listener
for both sync and async processing.
2012-09-29 18:05:08 +02:00
Kristian S. Stangeland 23e676533a Beginning to add support for asynchronous packet listeners. 2012-09-29 01:00:12 +02:00
Kristian S. Stangeland 2012698275 Just in case, try to load the generated class first.
We might have generated it from a previous run.
2012-09-27 06:04:29 +02:00
Kristian S. Stangeland 42303ae0fc Use the FieldAccessException instead of InvalidArgumentException.
Otherwise, the CompiledStructureModifier has a leaky abstraction.
2012-09-27 05:42:34 +02:00
Kristian S. Stangeland 0004fb530d Update the package name in the compiler. 2012-09-27 05:28:52 +02:00
Kristian S. Stangeland ea823e58f7 Move structure modifier compiler to the reflect package. 2012-09-27 05:28:13 +02:00
Kristian S. Stangeland 8293c71a4c Make sure the packet IDs are actually valid. 2012-09-27 05:26:02 +02:00
Kristian S. Stangeland eb8abd4635 Try to clean up after a failed hook. 2012-09-27 05:22:08 +02:00
Kristian S. Stangeland 240df9dc7a Complicated feature - auto-compilation of structure modifier.
Using ASM we can automatically generate a faster structure modifier
that doesn't use reflection to read or write public fields.

Note that because the compilation itself is a bit slow (10 ms++), 
we have to create a background compilation thread. Future work:
 * Disable the thread if it's idle after 60 seconds.
 * Don't recreate the thread when the server reloads.
2012-09-27 03:24:34 +02:00
Kristian S. Stangeland 90970d1b9b Ensure that server operators are informed about incompatibility. 2012-09-26 06:00:26 +02:00
Kristian S. Stangeland 9efb85e7c3 Handle 1.3 correctly. 2012-09-26 04:06:10 +02:00
Kristian S. Stangeland 22f2f45d1e Added support for a hook method that can intercept map chunk packets. 2012-09-26 03:31:59 +02:00
Kristian S. Stangeland e04a78fc04 Added yet another player hook method.
This method works by injecting into the NetServerHandler object
of a player. That way, we can also intercept map chunk packets.
2012-09-26 02:26:44 +02:00
Kristian S. Stangeland 9e402a3ab4 Added a "canInject" property in the injectors. 2012-09-26 02:01:44 +02:00
Kristian S. Stangeland d8b300e3a6 Try to get the local player object if possible. 2012-09-25 23:10:08 +02:00
Kristian S. Stangeland e0c03186c3 Add support for Java serialization of PacketEvent. It might be useful. 2012-09-25 23:09:02 +02:00
Kristian S. Stangeland 7f69c0204d Added a bit of commentary. 2012-09-25 17:13:23 +02:00
Kristian S. Stangeland 8839c03948 Fixed a number of simple bugs discovered by FindBugs. 2012-09-20 18:35:45 +02:00
Kristian S. Stangeland 3899704774 Merge pull request #1 from mbax/mbax
TagAPI is compatible as of version 1.7
2012-09-19 11:00:18 -07:00
mbax 9ad6dcb41c TagAPI is compatible as of version 1.7 2012-09-19 13:47:38 -04:00
Kristian S. Stangeland 54242debaa Made structure modifiers more overridable. 2012-09-19 19:25:11 +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 bc7b395889 Adding desperately needed async/sync documentation. 2012-09-19 03:48:46 +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 77376a2fa3 Make the Bukkit unwrapper use the actual return type. 2012-09-18 18:37:11 +02:00
Kristian S. Stangeland 99b9dc90ae Adding the new javadoc. 2012-09-18 17:27:59 +02:00
Kristian S. Stangeland cd9fa1cdf6 Fixing documentation. 2012-09-18 17:20:48 +02:00
Kristian S. Stangeland ad6fbae5d6 Incrementing to version 1.1.0 2012-09-18 17:17:20 +02:00
Kristian S. Stangeland 8f9939f65c Added the ability to refresh the apperance of entities and players. 2012-09-18 16:30:46 +02:00
Kristian S. Stangeland 3783328cb1 Forgot to convert the types to NMS. 2012-09-18 02:16:11 +02:00
Kristian S. Stangeland 140edabb4e Fixed packet constructor and tons of other bugs. 2012-09-18 02:04:26 +02:00
Kristian S. Stangeland ffbaed283a Improved the API slightly. 2012-09-18 00:01:02 +02:00
Kristian S. Stangeland e5beca4ea5 And the PacketConstructor class itself. 2012-09-17 22:59:41 +02:00
Kristian S. Stangeland fb15ee1b22 Adding packet constructors.
This should simplify a lot of code.
2012-09-17 22:59:20 +02:00
Kristian S. Stangeland 7a60c5aac7 Made the server registry static. 2012-09-17 16:40:08 +02:00
Kristian S. Stangeland f74a6e9832 Made the packet ids static and thus usable in a switch statement. 2012-09-17 16:38:26 +02:00
Kristian S. Stangeland 381484fc5d Adding a simple packet registry. 2012-09-17 16:21:15 +02:00
Kristian S. Stangeland 455ffca63f Adding a entity structure modifier. 2012-09-17 01:34:10 +02:00
Kristian S. Stangeland 286e1159e9 Made certain classes private. No need to expose them to the API. 2012-09-16 03:18:58 +02:00
Kristian S. Stangeland 62d9afd1d1 Readme spelling. 2012-09-16 02:43:31 +02:00
Kristian S. Stangeland 59a3ba82b8 Changed formating again. Added different levels. 2012-09-16 02:27:33 +02:00
Kristian S. Stangeland adb57e2bf3 Fixed readme formating somewhat. 2012-09-16 02:22:34 +02:00
Kristian S. Stangeland 798d734ba9 Adding readme. 2012-09-16 02:17:38 +02:00
Kristian S. Stangeland b299886b62 Fixed the old server listener. 2012-09-16 02:07:18 +02:00
Kristian S. Stangeland 913ff7eed1 Removed pointless imports. 2012-09-16 01:41:08 +02:00
Kristian S. Stangeland d04a666bc5 Readded a lighter version of network object injector.
This version only supports CraftBukkit 1.3.0 and later.
2012-09-15 23:07:14 +02:00
Kristian S. Stangeland bdfbfa47c8 The NetworkObjectInjector is too unstable, so we'll remove it. 2012-09-15 20:10:35 +02:00
Kristian S. Stangeland 57c720681a Made the object injector somewhat workable. 2012-09-15 20:09:34 +02:00
Kristian S. Stangeland 7e28aefb75 Moved the instance generator to a separate package.
In addition, added a new injection method. It's still not
finalized, and may get removed in the end.
2012-09-15 19:12:59 +02:00
Kristian S. Stangeland aa2dcefa0d Added two different player injection hooks, for compatibility with
TagAPI.
2012-09-15 13:47:14 +02:00
Kristian S. Stangeland 616213924b Refactored the SortedCopyOnWriteArray out into a separate package.
Made it extend the java.util.Collection interface.
2012-09-15 13:07:44 +02:00
Kristian S. Stangeland fffa3881dd We won't try to fix TagAPI. Instead, we'll warn the users. 2012-09-15 02:39:38 +02:00
Kristian S. Stangeland 85f7360109 Refactored the hacked array list out of the player injector. 2012-09-15 01:58:13 +02:00
Kristian S. Stangeland f2effab907 Improved documentation in a few places. 2012-09-15 01:04:08 +02:00
Kristian S. Stangeland 4505e31965 Prevented concurrency issues.
The SortedCopyOnWriteArray didn't wrap the iterator() in a
unmodifiable iterator. In addition, ConcurrentListenerMultimap
incorrectly used the iterator to remove objects from this Array. 

Added a "remove(T value)" method that is thread-safe.
2012-09-14 22:33:15 +02:00
Kristian S. Stangeland 88dcf0cb32 Improved API and performance.
API changes:
 * The PacketListener now uses a "ListeningWhitelist" class 
   to report which packet ids it wishes to listen in on for
   either the client or the server. This makes it possible to 
   use your plugin class as the listener more easily.
   
 * Added a priority system similar to Bukkit events.
   
Performance changes:
 * Create and maintain a separate list of listeners for each packet
   ID. This uses slightly more memory, but is far more efficient. 
   Especially in light of the priority system.

In addition, the listener lists are (hopefully) concurrent. They're
optimized for read-access, however. Adding or removing a listener
is a O(n) operation.
2012-09-14 19:12:08 +02:00
Kristian S. Stangeland bb8bec907c Added a priorty to every listener. Improved performance. 2012-09-14 12:41:57 +02:00
Kristian S. Stangeland 45169905fe Fix documentation. 2012-09-13 21:15:30 +02:00
Kristian S. Stangeland 783281ca2c Made "metrics" invisible. No point polluting the namespace. 2012-09-13 21:08:31 +02:00
Kristian S. Stangeland 0d35ab6038 Adding the license information. Using GPL2 as of now.
While libraries are sometimes licensed under LGPL, or even less
restrictive licenses, in this particular case it doesn't really
make sense to encourage direct copying. The problem is that 
competing libraries would conflict with each other. It's better
to feed improvements back into a single project.

Though, it's of course possible to fork this project. You can change
the internal classes - for instance, improving or changing the hook
points - without worriying about the public API.
2012-09-13 21:04:46 +02:00
Kristian S. Stangeland 8bb397b90e Adding the ItemDisguise example program. 2012-09-13 20:47:21 +02:00
Kristian S. Stangeland 0653dc8c15 Made the "getPlugin" method mandatory for all listeners.
It's pretty useful for the data collector, as well as the utility
method "removePacketListeners".
2012-09-13 17:29:37 +02:00
Kristian S. Stangeland ed5ce19795 Added a plugin users report to metrics. 2012-09-13 17:06:48 +02:00
Kristian S. Stangeland 83f313ce89 Switching to Metrics full. 2012-09-13 16:46:11 +02:00
Kristian S. Stangeland c02ffb569d Adding metrics (lite). 2012-09-13 16:08:55 +02:00
Kristian S. Stangeland 1710ef343b Syncronize before hacking into the packet list. In addition,
copy over any unprocessed packets into the new list.

When we're done, we'll also clean up properly by copying over 
any remaining packets.
2012-09-13 16:08:41 +02:00
Kristian S. Stangeland 5a6f3be219 Fix the server packet cancelling feature. 2012-09-13 14:56:14 +02:00
Kristian S. Stangeland 16d231f778 Fixed test case. 2012-09-13 14:43:15 +02:00
Kristian S. Stangeland 0d531ecc4b Added a custom exception. Simplified the event API. 2012-09-13 14:42:37 +02:00
Kristian S. Stangeland 353302fe5f Changed the API slightly. Added WorldType to PacketContainer. 2012-09-13 14:25:01 +02:00
Kristian S. Stangeland 035277bdeb Added support for at least Beta 1.8.
This involved a bunch of reflection magic, along with the removal
of every Apache Commons reference, in addition to every internal
Guava class.
2012-09-13 13:57:22 +02:00
Kristian S. Stangeland f372b247f9 Add support for 1.3.2 2012-09-13 11:34:47 +02:00
Kristian S. Stangeland 4307cb9104 Added the default instance class. 2012-09-13 11:01:32 +02:00
Kristian S. Stangeland da694ca5ed Added support for default values in packets. 2012-09-13 11:01:08 +02:00
358 changed files with 71660 additions and 4015 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!
+11
View File
@@ -0,0 +1,11 @@
_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
- [ ] You didn't use `/reload`
Debug paste link:
Description and relevant errors:
+29
View File
@@ -0,0 +1,29 @@
---
name: API question
about: Questions related to using ProtocolLib in your plugins
title: ''
labels: ''
assignees: ''
---
**Make sure you're doing 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
- [ ] You didn't use `/reload`
**Describe the question**
A clear and concise description of what your question is.
**API method(s) used**
List what API method(s) you're using
**Expected behavior**
A clear and concise description of what you expected to happen.
**Code**
If applicable, add relevant code from your project
**Additional context**
Add any other context about the problem here.
+30
View File
@@ -0,0 +1,30 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Version Info**
Provide your ProtocolLib install info with `/protocol dump` through pastebin.
**Additional context**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+182
View File
@@ -0,0 +1,182 @@
#################
## 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/
.gradle/
build/
logs/
+11
View File
@@ -0,0 +1,11 @@
language: java
jdk:
- openjdk8
script: mvn clean test
install: true
notifications:
email:
recipients:
- "dmulloy2@live.com"
on_success: change
on_failure: always
+339
View File
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
-14
View File
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" output="class_test" path="test"/>
<classpathentry kind="lib" path="D:/Games/Minecraft/Server Mods/Server/craftbukkit-1.3.1-R2.0.jar"/>
<classpathentry kind="lib" path="lib/cglib-nodep-2.2.3.jar">
<attributes>
<attribute name="javadoc_location" value="jar:file:/D:/Kristian/Programmer/Binaries/cglib.2.2.3/cglib-docs-2.2.3.jar!/"/>
</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/jre6"/>
<classpathentry kind="output" path="class"/>
</classpath>
-17
View File
@@ -1,17 +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>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -1,11 +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.source=1.6
-14
View File
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project default="compile" name="Project ItemDisguise" basedir=".">
<!--ANT 1.7 is required -->
<target name="compile">
<jar destfile="bin/ProtocolLib.jar" filesetmanifest="mergewithoutmain" excludes="test/*">
<manifest>
<attribute name="Main-Class" value="com.comphenix.itemdisguise.Application"/>
<attribute name="Class-Path" value="."/>
</manifest>
<fileset dir="class"/>
<zipfileset excludes="META-INF/*.SF" src="lib/cglib-nodep-2.2.3.jar"/>
</jar>
</target>
</project>
Binary file not shown.
@@ -1,8 +0,0 @@
package com.comphenix.protocol;
public class Application {
public static void main(String[] args) {
// For now, though we might consider making a proper application
System.out.println("This is a Bukkit library. Place it in the plugin-folder and restart the server!");
}
}
@@ -1,63 +0,0 @@
package com.comphenix.protocol;
import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.injector.PacketFilterManager;
public class ProtocolLibrary extends JavaPlugin {
// There should only be one protocol manager, so we'll make it static
private static PacketFilterManager protocolManager;
// Error logger
private Logger logger;
@Override
public void onLoad() {
logger = getLoggerSafely();
protocolManager = new PacketFilterManager(getClassLoader(), logger);
}
@Override
public void onEnable() {
Server server = getServer();
PluginManager manager = server.getPluginManager();
// Player login and logout events
protocolManager.registerEvents(manager, this);
protocolManager.initializePlayers(server.getOnlinePlayers());
}
@Override
public void onDisable() {
protocolManager.close();
}
/**
* Retrieves the packet protocol manager.
* @return Packet protocol manager.
*/
public static ProtocolManager getProtocolManager() {
return protocolManager;
}
// 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,90 +0,0 @@
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import java.util.Set;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.google.common.collect.ImmutableSet;
public interface ProtocolManager {
/**
* Retrieves a list of every registered packet listener.
* @return Every registered packet listener.
*/
public abstract ImmutableSet<PacketListener> getPacketListeners();
/**
* Adds a packet listener.
* @param listener - new packet listener.
*/
public abstract void addPacketListener(PacketListener listener);
/**
* Removes a given packet listener.
* @param listener - the packet listener to remove.
*/
public abstract void removePacketListener(PacketListener listener);
/**
* 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;
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* @param id - packet ID.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(int id);
/**
* Retieves a set of every enabled packet.
* @return Every packet filter.
*/
public Set<Integer> getPacketFilters();
/**
* Determines whether or not is protocol mananger has been disabled.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClosed();
}
@@ -1,31 +0,0 @@
package com.comphenix.protocol.events;
/**
* 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;
}
}
@@ -1,60 +0,0 @@
package com.comphenix.protocol.events;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.bukkit.plugin.java.JavaPlugin;
import com.google.common.collect.Sets;
/**
* Represents a packet listener with useful constructors.
*
* @author Kristian
*/
public abstract class PacketAdapter implements PacketListener {
protected JavaPlugin plugin;
protected Set<Integer> packetsID;
protected ConnectionSide connectionSide;
/**
* Initialize a packet listener.
* @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(JavaPlugin plugin, ConnectionSide connectionSide, Integer... packets) {
this.plugin = plugin;
this.connectionSide = connectionSide;
this.packetsID = Sets.newHashSet(packets);
}
@Override
public void onPacketReceiving(PacketEvent event) {
// Default is to do nothing
}
@Override
public void onPacketSending(PacketEvent event) {
// And here too
}
@Override
public ConnectionSide getConnectionSide() {
return connectionSide;
}
@Override
public Set<Integer> getPacketsID() {
return packetsID;
}
@Override
public String toString() {
// This is used by the error reporter
return String.format("PacketAdapter[plugin=%s, side=%s, packets=%s]",
plugin.getName(), getConnectionSide().name(),
StringUtils.join(packetsID, ", "));
}
}
@@ -1,126 +0,0 @@
package com.comphenix.protocol.events;
import org.apache.commons.lang.NullArgumentException;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.injector.StructureCache;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import net.minecraft.server.Packet;
/**
* Represents a Minecraft packet indirectly.
*
* @author Kristian
*/
public class PacketContainer {
protected Packet handle;
protected int id;
// Current structure modifier
protected StructureModifier<Object> structureModifier;
/**
* 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 NullArgumentException("handle");
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;
}
public <T> StructureModifier<T> getPrimitiveModifier(Class<T> primitiveType) {
return structureModifier.withType(primitiveType);
}
public StructureModifier<ItemStack> getItemModifier() {
// Convert from and to the Bukkit wrapper
return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class, new EquivalentConverter<ItemStack>() {
public Object getGeneric(ItemStack specific) {
return ((CraftItemStack) specific).getHandle();
}
@Override
public ItemStack getSpecific(Object generic) {
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
}
});
}
public StructureModifier<ItemStack[]> getItemArrayModifier() {
// Convert to and from the Bukkit wrapper
return structureModifier.<ItemStack[]>withType(net.minecraft.server.ItemStack[].class, 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] = ((CraftItemStack) specific[i]).getHandle();
}
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;
}
});
}
/**
* Retrieves the ID of this packet.
* @return Packet ID.
*/
public int getID() {
return id;
}
}
@@ -1,119 +0,0 @@
package com.comphenix.protocol.events;
import java.util.EventObject;
import org.bukkit.entity.Player;
public class PacketEvent extends EventObject {
/**
* Automatically generated by Eclipse.
*/
private static final long serialVersionUID = -5360289379097430620L;
private PacketContainer packet;
private Player sender;
private Player reciever;
private boolean cancel;
/**
* 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 sender, Player reciever) {
super(source);
this.packet = packet;
this.sender = sender;
this.reciever = reciever;
}
/**
* 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, null);
}
/**
* 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, null, recipient);
}
/**
* 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.
* @return The sender, or NULL if the server is sending the packet.
*/
public Player getSender() {
return sender;
}
/**
* Retrieves the player that will recieve the packet.
* @return The reciever, or NULL if the server is recieving the packet.
*/
public Player getReciever() {
return reciever;
}
/**
* Whether or not this packet was created by the server.
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
*/
public boolean isServerPacket() {
return getReciever() != null;
}
}
@@ -1,34 +0,0 @@
package com.comphenix.protocol.events;
import java.util.Set;
public interface PacketListener {
/**
* Invoked right before a packet is transmitted from the server to the client.
* <p>
* Note that the packet may be replaced, if needed.
*
* @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.
* @param event - the packet that has been recieved.
*/
public void onPacketReceiving(PacketEvent event);
/**
* Retrieve whether or not we're listening for client or server packets.
* @return The type of packets we expect.
*/
public ConnectionSide getConnectionSide();
/**
* Set of packet ids we expect to recieve.
* @return Packets IDs.
*/
public Set<Integer> getPacketsID();
}
@@ -1,88 +0,0 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.ObjectUtils;
import net.minecraft.server.Packet;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
/**
* Static registries in Minecraft.
*
* @author Kristian
*/
@SuppressWarnings("rawtypes")
class MinecraftRegistry {
// The packet class to packet ID translator
private static Map<Class, Integer> packetToID;
// 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 = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("java\\.util\\.Map");
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
}
}
return packetToID;
}
public static Map<Integer, Class> getOverwrittenPackets() {
return overwrittenPackets;
}
public static Map<Integer, Class> getPreviousPackets() {
return previousValues;
}
/**
* 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 vanilla - 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(packetToID)) {
return lookup.get(packetToID);
}
// Will most likely not be used
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
if (ObjectUtils.equals(entry.getValue(), packetID)) {
return entry.getKey();
}
}
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
}
}
@@ -1,336 +0,0 @@
package com.comphenix.protocol.injector;
import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.Packet;
import org.apache.commons.lang.NullArgumentException;
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.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
public final class PacketFilterManager implements ProtocolManager {
private Set<PacketListener> packetListeners = new CopyOnWriteArraySet<PacketListener>();
// Player injection
private Map<DataInputStream, Player> connectionLookup = new HashMap<DataInputStream, Player>();
private Map<Player, PlayerInjector> playerInjection = new HashMap<Player, PlayerInjector>();
// Packet injection
private PacketInjector packetInjector;
// Enabled packet filters
private Set<Integer> packetFilters = new HashSet<Integer>();
// Whether or not this class has been closed
private boolean hasClosed;
// Error logger
private Logger logger;
/**
* Only create instances of this class if protocol lib is disabled.
*/
public PacketFilterManager(ClassLoader classLoader, Logger logger) {
if (logger == null)
throw new NullArgumentException("logger");
if (classLoader == null)
throw new NullArgumentException("classLoader");
try {
// Initialize values
this.logger = logger;
this.packetInjector = new PacketInjector(classLoader, this, connectionLookup);
} catch (IllegalAccessException e) {
logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
}
}
public Logger getLogger() {
return logger;
}
@Override
public ImmutableSet<PacketListener> getPacketListeners() {
return ImmutableSet.copyOf(packetListeners);
}
@Override
public void addPacketListener(PacketListener listener) {
if (listener == null)
throw new NullArgumentException("listener");
packetListeners.add(listener);
enablePacketFilters(listener.getConnectionSide(),
listener.getPacketsID());
}
@Override
public void removePacketListener(PacketListener listener) {
if (listener == null)
throw new NullArgumentException("listener");
packetListeners.remove(listener);
disablePacketFilters(listener.getConnectionSide(),
listener.getPacketsID());
}
/**
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public void invokePacketRecieving(PacketEvent event) {
for (PacketListener listener : packetListeners) {
try {
if (canHandlePacket(listener, event))
listener.onPacketReceiving(event);
} catch (Exception e) {
// Minecraft doesn't want your Exception.
logger.log(Level.SEVERE, "Exception occured in onPacketReceiving() for " + listener.toString(), e);
}
}
}
/**
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public void invokePacketSending(PacketEvent event) {
for (PacketListener listener : packetListeners) {
try {
if (canHandlePacket(listener, event))
listener.onPacketSending(event);
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception occured in onPacketReceiving() for " + listener.toString(), e);
}
}
}
private boolean canHandlePacket(PacketListener listener, PacketEvent event) {
// Make sure the listener is looking for this packet
if (!listener.getPacketsID().contains(event.getPacket().getID()))
return false;
// And this type of packet
if (event.isServerPacket())
return listener.getConnectionSide().isForServer();
else
return listener.getConnectionSide().isForClient();
}
/**
* Enables packet events for a given packet ID.
* <p>
* Note that all packets are disabled by default.
*
* @param side - which side the event will arrive from.
* @param packets - the packet id(s).
*/
private void enablePacketFilters(ConnectionSide side, Set<Integer> packets) {
if (side == null)
throw new NullArgumentException("side");
for (int packetID : packets) {
if (side.isForServer())
packetFilters.add(packetID);
if (side.isForClient() && packetInjector != null)
packetInjector.addPacketHandler(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, Set<Integer> packets) {
if (side == null)
throw new NullArgumentException("side");
for (int packetID : packets) {
if (side.isForServer())
packetFilters.remove(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 NullArgumentException("reciever");
if (packet == null)
throw new NullArgumentException("packet");
getInjector(reciever).sendServerPacket(packet.getHandle(), 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 NullArgumentException("sender");
if (packet == null)
throw new NullArgumentException("packet");
PlayerInjector injector = getInjector(sender);
Packet mcPacket = packet.getHandle();
if (filters) {
mcPacket = injector.handlePacketRecieved(mcPacket);
}
injector.processPacket(mcPacket);
}
@Override
public PacketContainer createPacket(int id) {
return new PacketContainer(id);
}
@Override
public Set<Integer> getPacketFilters() {
if (packetInjector != null)
return Sets.union(packetFilters, packetInjector.getPacketHandlers());
else
return packetFilters;
}
/**
* Initialize the packet injection for every player.
* @param players - list of players to inject.
*/
public void initializePlayers(Player[] players) {
for (Player player : players)
injectPlayer(player);
}
private void injectPlayer(Player player) {
// Don't inject if the class has closed
if (!hasClosed && player != null && !playerInjection.containsKey(player)) {
try {
PlayerInjector injector = new PlayerInjector(player, this, packetFilters);
injector.injectManager();
playerInjection.put(player, injector);
connectionLookup.put(injector.getInputStream(false), player);
} catch (IllegalAccessException e) {
// Mark this injection attempt as a failure
playerInjection.put(player, null);
logger.log(Level.SEVERE, "Unable to access fields.", e);
}
}
}
/**
* 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, Plugin plugin) {
manager.registerEvents(new Listener() {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent event) {
injectPlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerQuit(PlayerQuitEvent event) {
uninjectPlayer(event.getPlayer());
}
}, plugin);
}
private void uninjectPlayer(Player player) {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.get(player);
DataInputStream input = injector.getInputStream(true);
if (injector != null) {
injector.cleanupAll();
playerInjection.remove(injector);
connectionLookup.remove(input);
}
}
}
private PlayerInjector getInjector(Player player) {
if (!playerInjection.containsKey(player)) {
// What? Try to inject again.
injectPlayer(player);
}
PlayerInjector injector = playerInjection.get(player);
// Check that the injector was sucessfully added
if (injector != null)
return injector;
else
throw new IllegalArgumentException("Player has no injected handler.");
}
@Override
public boolean isClosed() {
return hasClosed;
}
public void close() {
// Guard
if (hasClosed)
return;
// Remove everything
for (PlayerInjector injection : playerInjection.values()) {
injection.cleanupAll();
}
// Remove packet handlers
if (packetInjector != null)
packetInjector.cleanupAll();
playerInjection.clear();
connectionLookup.clear();
hasClosed = true;
}
@Override
protected void finalize() throws Throwable {
close();
}
}
@@ -1,177 +0,0 @@
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 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.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
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 PacketFilterManager manager;
// Allows us to determine the sender
private Map<DataInputStream, Player> playerLookup;
// Class loader
private ClassLoader classLoader;
public PacketInjector(ClassLoader classLoader, PacketFilterManager manager,
Map<DataInputStream, Player> playerLookup) throws IllegalAccessException {
this.classLoader = classLoader;
this.manager = manager;
this.playerLookup = playerLookup;
initialize();
}
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).getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
try {
intHashMap = intHashMapField.get(null);
} 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);
// 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.setUseCache(false);
ex.setClassLoader(classLoader);
Class proxy = ex.createClass();
// Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] {
new ReadPacketModifier(packetID, this)
});
try {
// Override values
putMethod.invoke(intHashMap, packetID, proxy);
previous.put(packetID, old);
registry.put(proxy, packetID);
overwritten.put(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);
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 = playerLookup.get(input);
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client);
manager.invokePacketRecieving(event);
return event;
}
@SuppressWarnings("rawtypes")
public void cleanupAll() {
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
// Remove every packet handler
for (Integer id : previous.keySet()) {
removePacketHandler(id);
}
overwritten.clear();
previous.clear();
}
}
@@ -1,261 +0,0 @@
package com.comphenix.protocol.injector;
import java.io.DataInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Set;
import net.minecraft.server.EntityPlayer;
import net.minecraft.server.Packet;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
class PlayerInjector {
// Cache previously retrieved fields
private static Field serverHandlerField;
private static Field networkManagerField;
private static Field inputField;
private static Field netHandlerField;
// And methods
private static Method queueMethod;
private static Method processMethod;
private Player player;
private boolean hasInitialized;
// Reference to the player's network manager
private VolatileField networkManager;
// The packet manager and filters
private PacketFilterManager manager;
private Set<Integer> packetFilters;
// Previous data input
private DataInputStream cachedInput;
// Current net handler
private Object netHandler;
public PlayerInjector(Player player, PacketFilterManager manager, Set<Integer> packetFilters) throws IllegalAccessException {
this.player = player;
this.manager = manager;
this.packetFilters = packetFilters;
initialize();
}
private void initialize() throws IllegalAccessException {
CraftPlayer craft = (CraftPlayer) player;
EntityPlayer notchEntity = craft.getHandle();
if (!hasInitialized) {
// Do this first, in case we encounter an exception
hasInitialized = true;
// Retrieve the server handler
if (serverHandlerField == null)
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
Object serverHandler = FieldUtils.readField(serverHandlerField, notchEntity);
// Next, get the network manager
if (networkManagerField == null)
networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(".*NetworkManager");
networkManager = new VolatileField(networkManagerField, serverHandler);
// And the queue method
if (queueMethod == null)
queueMethod = FuzzyReflection.fromClass(networkManagerField.getType()).
getMethodByParameters("queue", Packet.class );
// And the data input stream that we'll use to identify a player
if (inputField == null)
inputField = FuzzyReflection.fromObject(networkManager.getOldValue(), true).
getFieldByType("java\\.io\\.DataInputStream");
}
}
/**
* Retrieves the current net handler for this player.
* @return Current net handler.
* @throws IllegalAccessException Unable to find or retrieve net handler.
*/
private Object getNetHandler() throws IllegalAccessException {
// What a mess
try {
if (netHandlerField == null)
netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true).
getFieldByType("net\\.minecraft\\.NetHandler");
} catch (RuntimeException e1) {
try {
// Well, that sucks. Try just Minecraft objects then.
netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true).
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
} catch (RuntimeException e2) {
return new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
}
}
// Get the handler
if (netHandler != null)
netHandler = FieldUtils.readField(netHandlerField, networkManager.getOldValue(), true);
return netHandler;
}
/**
* Processes the given packet as if it was transmitted by the current player.
* @param packet - packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
public void processPacket(Packet packet) throws IllegalAccessException, InvocationTargetException {
Object netHandler = getNetHandler();
// Get the process method
if (processMethod == null) {
try {
processMethod = FuzzyReflection.fromClass(Packet.class).
getMethodByParameters("processPacket", netHandlerField.getType());
} catch (RuntimeException e) {
throw new IllegalArgumentException("Cannot locate process packet method: " + e.getMessage());
}
}
// We're ready
try {
processMethod.invoke(packet, netHandler);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Method " + processMethod.getName() + " is not compatible.");
} catch (InvocationTargetException e) {
throw e;
}
}
/**
* Send a packet to the client.
* @param packet - server packet to send.
* @param filtered - whether or not the packet will be filtered by our listeners.
* @param InvocationTargetException If an error occured when sending the packet.
*/
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
Object networkDelegate = filtered ? networkManager.getValue() : networkManager.getOldValue();
if (networkDelegate != null) {
try {
// Note that invocation target exception is a wrapper for a checked exception
queueMethod.invoke(networkDelegate, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access queue method.", e);
}
} else {
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
}
}
public void injectManager() {
if (networkManager != null) {
final Class<?> networkInterface = networkManagerField.getType();
final Object networkDelegate = networkManager.getOldValue();
// Create our proxy object
Object networkProxy = Proxy.newProxyInstance(networkInterface.getClassLoader(),
new Class<?>[] { networkInterface }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// OH OH! The queue method!
if (method.equals(queueMethod)) {
Packet packet = (Packet) args[0];
if (packet != null) {
packet = handlePacketRecieved(packet);
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
}
}
// Delegate to our underlying class
try {
return method.invoke(networkDelegate, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
});
// Inject it, if we can.
networkManager.setValue(networkProxy);
}
}
/**
* Allows a packet to be recieved by the listeners.
* @param packet - packet to recieve.
* @return The given packet, or the packet replaced by the listeners.
*/
Packet handlePacketRecieved(Packet packet) {
// Get the packet ID too
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
// Make sure we're listening
if (packetFilters.contains(id)) {
// A packet has been sent guys!
PacketContainer container = new PacketContainer(id, packet);
PacketEvent event = PacketEvent.fromServer(manager, container, player);
manager.invokePacketSending(event);
// Cancelling is pretty simple. Just ignore the packet.
if (event.isCancelled())
return null;
// Right, remember to replace the packet again
return event.getPacket().getHandle();
}
return packet;
}
public DataInputStream getInputStream(boolean cache) {
// Get the associated input stream
try {
if (cache && cachedInput != null)
return cachedInput;
// Save to cache
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager.getOldValue(), true);
return cachedInput;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read input stream.", e);
}
}
public void cleanupAll() {
// Clean up
networkManager.revertValue();
}
}
@@ -1,84 +0,0 @@
package com.comphenix.protocol.injector;
import java.io.DataInputStream;
import java.lang.reflect.Method;
import java.util.WeakHashMap;
import org.apache.commons.lang.ArrayUtils;
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 };
// Common for all packets of the same type
private PacketInjector packetInjector;
private int packetID;
// Whether or not a packet has been cancelled
private static WeakHashMap<Object, Object> override = new WeakHashMap<Object, Object>();
public ReadPacketModifier(int packetID, PacketInjector packetInjector) {
this.packetID = packetID;
this.packetInjector = packetInjector;
}
@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);
}
if (override.containsKey(thisObj)) {
Object overridenObject = override.get(thisObj);
// Cancel EVERYTHING, including "processPacket"
if (overridenObject == null)
return null;
returnValue = proxy.invokeSuper(overridenObject, args);
} else {
returnValue = proxy.invokeSuper(thisObj, args);
}
// Is this a readPacketData method?
if (returnValue == null &&
ArrayUtils.isEquals(method.getParameterTypes(), parameters)) {
// 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);
Packet result = event.getPacket().getHandle();
// Handle override
if (event != null) {
if (event.isCancelled()) {
override.put(thisObj, null);
} else if (!objectEquals(thisObj, result)) {
override.put(thisObj, result);
}
}
}
return returnValue;
}
private boolean objectEquals(Object a, Object b) {
return System.identityHashCode(a) != System.identityHashCode(b);
}
}
@@ -1,46 +0,0 @@
package com.comphenix.protocol.injector;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.server.Packet;
import com.comphenix.protocol.reflect.StructureModifier;
public class StructureCache {
// Structure modifiers
private static Map<Integer, StructureModifier<Object>> structureModifiers = new HashMap<Integer, StructureModifier<Object>>();
/**
* 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) {
StructureModifier<Object> result = structureModifiers.get(id);
// Use the vanilla class definition
if (result == null) {
result = new StructureModifier<Object>(MinecraftRegistry.getPacketClassFromID(id, true));
structureModifiers.put(id, result);
}
return result;
}
}
@@ -1,12 +0,0 @@
package com.comphenix.protocol.reflect;
/**
* Interface that converts generic objects into types and back.
*
* @author Kristian
* @param <TType> The specific type.
*/
public interface EquivalentConverter<TType> {
public TType getSpecific(Object generic);
public Object getGeneric(TType specific);
}
@@ -1,218 +0,0 @@
package com.comphenix.protocol.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.lang.ArrayUtils;
/**
* Retrieves fields and methods by signature, not just name.
*
* @author Kristian
*/
public class FuzzyReflection {
/**
* Matches a Minecraft object.
*/
public static String MINECRAFT_OBJECT = "net\\.minecraft(\\.\\w+)+";
// The class we're actually representing
private Class<?> source;
// Whether or not to lookup private members
private boolean forceAccess;
public FuzzyReflection(Class<?> source, boolean forceAccess) {
this.source = source;
this.forceAccess = forceAccess;
}
/**
* Retrieves a fuzzy reflection instance from a given class.
* @param source - the class we'll use.
* @return A fuzzy reflection instance.
*/
public static FuzzyReflection fromClass(Class<?> source) {
return fromClass(source, false);
}
/**
* Retrieves a fuzzy reflection instance from a given class.
* @param source - the class we'll use.
* @param forceAccess - whether or not to override scope restrictions.
* @return A fuzzy reflection instance.
*/
public static FuzzyReflection fromClass(Class<?> source, boolean forceAccess) {
return new FuzzyReflection(source, forceAccess);
}
/**
* Retrieves a fuzzy reflection instance from an object.
* @param reference - the object we'll use.
* @return A fuzzy reflection instance that uses the class of the given object.
*/
public static FuzzyReflection fromObject(Object reference) {
return new FuzzyReflection(reference.getClass(), false);
}
/**
* Retrieves a fuzzy reflection instance from an object.
* @param reference - the object we'll use.
* @param forceAccess - whether or not to override scope restrictions.
* @return A fuzzy reflection instance that uses the class of the given object.
*/
public static FuzzyReflection fromObject(Object reference, boolean forceAccess) {
return new FuzzyReflection(reference.getClass(), forceAccess);
}
/**
* Retrieves the underlying class.
*/
public Class<?> getSource() {
return source;
}
/**
* Retrieves a method by looking at its name.
* @param nameRegex - regular expression that will match method names.
* @return The first method that satisfies the regular expression.
*/
public Method getMethodByName(String nameRegex) {
Pattern match = Pattern.compile(nameRegex);
for (Method method : getMethods()) {
if (match.matcher(method.getName()).matches()) {
// Right - this is probably it.
return method;
}
}
throw new RuntimeException("Unable to find a method with the pattern " +
nameRegex + " in " + source.getName());
}
/**
* Retrieves a method by looking at the parameter types only.
* @param name - potential name of the method. Only used by the error mechanism.
* @param args - parameter types of the method to find.
* @return The first method that satisfies the parameter types.
*/
public Method getMethodByParameters(String name, Class<?>... args) {
// Find the correct method to call
for (Method method : getMethods()) {
if (ArrayUtils.isEquals(method.getParameterTypes(), args)) {
return method;
}
}
// That sucks
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
}
/**
* Retrieves a field by name.
* @param nameRegex - regular expression that will match a field name.
* @return The first field to match the given expression.
*/
public Field getFieldByName(String nameRegex) {
Pattern match = Pattern.compile(nameRegex);
for (Field field : getFields()) {
if (match.matcher(field.getName()).matches()) {
// Right - this is probably it.
return field;
}
}
// Looks like we're outdated. Too bad.
throw new RuntimeException("Unable to find a field with the pattern " +
nameRegex + " in " + source.getName());
}
/**
* Retrieves a field by type.
* <p>
* Note that the type is matched using the full canonical representation, i.e.:
* <ul>
* <li>java.util.List</li>
* <li>net.comphenix.xp.ExperienceMod</li>
* </ul>
* @param typeRegex - regular expression that will match the field type.
* @return The first field with a type that matches the given regular expression.
*/
public Field getFieldByType(String typeRegex) {
Pattern match = Pattern.compile(typeRegex);
// Like above, only here we test the field type
for (Field field : getFields()) {
if (match.matcher(field.getType().getName()).matches()) {
return field;
}
}
// Looks like we're outdated. Too bad.
throw new RuntimeException("Unable to find a field with the type " +
typeRegex + " in " + source.getName());
}
/**
* Retrieves all private and public fields in declared order (after JDK 1.5).
* @return Every field.
*/
public Set<Field> getFields() {
// We will only consider private fields in the declared class
if (forceAccess)
return setUnion(source.getDeclaredFields(), source.getFields());
else
return setUnion(source.getFields());
}
/**
* Retrieves all private and public methods in declared order (after JDK 1.5).
* @return Every method.
*/
public Set<Method> getMethods() {
// We will only consider private methods in the declared class
if (forceAccess)
return setUnion(source.getDeclaredMethods(), source.getMethods());
else
return setUnion(source.getMethods());
}
// Prevent duplicate fields
private static <T> Set<T> setUnion(T[]... array) {
Set<T> result = new LinkedHashSet<T>();
for (T[] elements : array) {
for (T element : elements) {
result.add(element);
}
}
return result;
}
/**
* Retrieves whether or not not to override any scope restrictions.
* @return TRUE if we override scope, FALSE otherwise.
*/
public boolean isForceAccess() {
return forceAccess;
}
/**
* Sets whether or not not to override any scope restrictions.
* @param forceAccess - TRUE if we override scope, FALSE otherwise.
*/
public void setForceAccess(boolean forceAccess) {
this.forceAccess = forceAccess;
}
}
@@ -1,218 +0,0 @@
package com.comphenix.protocol.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.base.Function;
import net.minecraft.server.Packet;
@SuppressWarnings("rawtypes")
public class StructureModifier<TField> {
// Object and its type
private Class targetType;
private Object target;
// Converter. May be NULL.
private EquivalentConverter<TField> converter;
// The fields to read in order
private Class fieldType;
private List<Field> data = new ArrayList<Field>();
// Cache of previous types
private Map<Class, StructureModifier> subtypeCache;
public StructureModifier(Class targetType) {
this(targetType, Object.class, getFields(targetType), null, new HashMap<Class, StructureModifier>());
}
private StructureModifier(Class targetType, Class fieldType, List<Field> data,
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
this.targetType = targetType;
this.fieldType = fieldType;
this.data = data;
this.converter = converter;
this.subtypeCache = subTypeCache;
}
private StructureModifier(StructureModifier<TField> other, Object target) {
this(other.targetType, other.fieldType, other.data, other.converter, other.subtypeCache);
this.target = target;
}
/**
* Reads the value of a field given its index.
* @param fieldIndex - index of the field.
* @return Value of the field.
* @throws IllegalAccessException If we're unable to read the field due to a security limitation.
*/
@SuppressWarnings("unchecked")
public TField read(int fieldIndex) throws IllegalAccessException {
if (fieldIndex < 0 || fieldIndex >= data.size())
throw new IllegalArgumentException("Field index must be within 0 - count");
if (target == null)
throw new IllegalStateException("Cannot read from a NULL target.");
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
// Use the converter, if we have it
if (converter != null)
return converter.getSpecific(result);
else
return (TField) result;
}
/**
* Writes the value of a field given its index.
* @param fieldIndex - index of the field.
* @param value - new value of the field.
* @return This structure modifier - for chaining.
* @throws IllegalAccessException If we're unable to write to the field due to a security limitation.
*/
public StructureModifier<TField> write(int fieldIndex, TField value) throws IllegalAccessException {
if (fieldIndex < 0 || fieldIndex >= data.size())
throw new IllegalArgumentException("Field index must be within 0 - count");
if (target == null)
throw new IllegalStateException("Cannot write to a NULL target.");
// Use the converter, if it exists
Object obj = converter != null ? converter.getGeneric(value) : value;
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
// Make this method chainable
return this;
}
/**
* Correctly modifies the value of a field.
* @param fieldIndex - index of the field to modify.
* @param select - the function that modifies the field value.
* @return This structure modifier - for chaining.
* @throws IllegalAccessException
*/
public StructureModifier<TField> modify(int fieldIndex, Function<TField, TField> select) throws IllegalAccessException {
TField value = read(fieldIndex);
return write(fieldIndex, select.apply(value));
}
/**
* Retrieves a structure modifier that only reads and writes fields of a given type.
* @param fieldType - the type, or supertype, of every field to modify.
* @return A structure modifier for fields of this type.
*/
public <T> StructureModifier<T> withType(Class fieldType) {
return withType(fieldType, null);
}
/**
* Retrieves a structure modifier that only reads and writes fields of a given type.
* @param fieldType - the type, or supertype, of every field to modify.
* @param converter - converts objects into the given type.
* @return A structure modifier for fields of this type.
*/
@SuppressWarnings("unchecked")
public <T> StructureModifier<T> withType(Class fieldType, EquivalentConverter<T> converter) {
StructureModifier<T> result = subtypeCache.get(fieldType);
if (fieldType.equals(this.fieldType)) {
// We're dealing with the exact field type.
return withConverter(converter);
} else if (result == null) {
List<Field> filtered = new ArrayList<Field>();
for (Field field : data) {
if (fieldType.isAssignableFrom(field.getType())) {
filtered.add(field);
}
}
// Cache structure modifiers
result = new StructureModifier<T>(targetType, fieldType, filtered,
converter, new HashMap<Class, StructureModifier>());
subtypeCache.put(fieldType, result);
}
// Add the target too
return result.withTarget(target);
}
/**
* Retrieves the common type of each field.
* @return Common type of each field.
*/
public Class getFieldType() {
return fieldType;
}
/**
* Retrieves the type of the object we're modifying.
* @return Type of the object.
*/
public Class getTargetType() {
return targetType;
}
/**
* Retrieves the object we're currently modifying.
* @return Object we're modifying.
*/
public Object getTarget() {
return target;
}
/**
* Retrieve the number of readable types.
* @return Readable types.
*/
public int size() {
return data.size();
}
/**
* Retrieves a structure modifier of the same type for a different object target.
* @param target - different target of the same type.
* @return Structure modifier with the new target.
*/
public StructureModifier<TField> withTarget(Object target) {
return new StructureModifier<TField>(this, target);
}
/**
* Retrieves a structure modifier with the same type and target, but using a new object converter.
* @param converter- the object converter to use.
* @return Structure modifier with the new converter.
*/
@SuppressWarnings("unchecked")
private <T> StructureModifier<T> withConverter(EquivalentConverter<T> converter) {
StructureModifier copy = new StructureModifier(this, target);
copy.converter = converter;
return copy;
}
// Used to filter out irrelevant fields
private static List<Field> getFields(Class type) {
List<Field> result = new ArrayList<Field>();
// Retrieve every private and public field
for (Field field : FuzzyReflection.fromClass(type, true).getFields()) {
int mod = field.getModifiers();
// Ignore static, final and "abstract packet" fields
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && !field.getDeclaringClass().equals(Packet.class)) {
result.add(field);
}
}
return result;
}
}
@@ -1,158 +0,0 @@
package com.comphenix.protocol.reflect;
import java.lang.reflect.Field;
/**
* Represents a field that will revert to its original state when this class is garbaged collected.
*
* @author Kristian
*/
public class VolatileField {
private Field field;
private Object container;
// The current and previous values
private Object previous;
private Object current;
// Whether or not we must reset or load
private boolean previousLoaded;
private boolean currentSet;
// Whether or not to break access restrictions
private boolean forceAccess;
/**
* Initializes a volatile field with an associated object.
* @param field - the field.
* @param container - the object this field belongs to.
*/
public VolatileField(Field field, Object container) {
this.field = field;
this.container = container;
}
/**
* Initializes a volatile field with an associated object.
* @param field - the field.
* @param container - the object this field belongs to.
* @param forceAccess - whether or not to override any scope restrictions.
*/
public VolatileField(Field field, Object container, boolean forceAccess) {
this.field = field;
this.container = container;
this.forceAccess = forceAccess;
}
/**
* Retrieves the current field.
* @return The stored field.
*/
public Field getField() {
return field;
}
/**
* Retrieves the object the field is stored.
* @return The reference object.
*/
public Object getContainer() {
return container;
}
/**
* Retrieves whether or not not to override any scope restrictions.
* @return TRUE if we override scope, FALSE otherwise.
*/
public boolean isForceAccess() {
return forceAccess;
}
/**
* Sets whether or not not to override any scope restrictions.
* @param forceAccess - TRUE if we override scope, FALSE otherwise.
*/
public void setForceAccess(boolean forceAccess) {
this.forceAccess = forceAccess;
}
/**
* Retrieves the current field value.
* @return The current field value.
*/
public Object getValue() {
// Retrieve the correct value
if (!currentSet) {
ensureLoaded();
return previous;
} else {
return current;
}
}
/**
* Retrieves the field value before the previous setValue(), unless saveValue() has been called.
* @return Previous value.
*/
public Object getOldValue() {
ensureLoaded();
return previous;
}
/**
* Sets the current value. This will be reverted unless saveValue() is called.
* @param newValue - new field value.
*/
public void setValue(Object newValue) {
// Remember to safe the previous value
ensureLoaded();
try {
FieldUtils.writeField(field, container, newValue, forceAccess);
current = newValue;
currentSet = true;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read field " + field.getName(), e);
}
}
/**
* Ensure that the current value is still set after this class has been garbaged collected.
*/
public void saveValue() {
previous = current;
currentSet = false;
}
/**
* Revert to the previously set value.
*/
public void revertValue() {
// Reset value.
if (currentSet) {
setValue(previous);
currentSet = false;
}
}
private void ensureLoaded() {
// Load the value if we haven't already
if (!previousLoaded) {
try {
previous = FieldUtils.readField(field, container, forceAccess);
previousLoaded = true;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read field " + field.getName(), e);
}
}
}
@Override
protected void finalize() throws Throwable {
revertValue();
}
}
-8
View File
@@ -1,8 +0,0 @@
name: ProtocolLib
version: 1.0.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,24 +0,0 @@
package com.comphenix.protocol.reflect;
import static org.junit.Assert.*;
import net.minecraft.server.Packet103SetSlot;
import org.junit.Test;
import com.comphenix.protocol.reflect.StructureModifier;
public class StructureModifierTest {
@Test
public void test() throws IllegalAccessException {
Packet103SetSlot move = new Packet103SetSlot();
StructureModifier<Object> modifier = new StructureModifier<Object>(Packet103SetSlot.class);
move.a = 1;
int value = (Integer) modifier.withTarget(move).withType(int.class).read(0);
assertEquals(1, value);
}
}
+169
View File
@@ -0,0 +1,169 @@
# 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
and outgoing [packets](https://www.wiki.vg/Protocol), or inject custom packets into the
stream. This is quite cumbersome to do, however, and most implementations will break
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
Critically, different plugins that use this approach may _hook_ into the same classes,
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](https://www.spigotmc.org/).
### Resources
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib)
* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc)
### Compilation
ProtocolLib is built with Maven and requires Spigot and SpigotAPI, which can be found [here](https://www.spigotmc.org/wiki/buildtools/).
### A new API
__ProtocolLib__ attempts to solve this problem by providing a event API, much like Bukkit,
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 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:
````yml
depend: [ProtocolLib]
````
You can also add ProtocolLib as a Maven dependency:
````xml
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
</repository>
...
</repositories>
<dependencies>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.5.0</version>
</dependency>
</dependencies>
````
Or use the maven dependency with gradle:
```gradle
repositories {
maven { url "https://repo.dmulloy2.net/nexus/repository/public/" }
}
dependencies {
compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.5.0";
}
```
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:
````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:
````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:
````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);
````
But with ProtocolLib, you can turn that into something more manageable. Notice that
you don't have to create an ArrayList with this version:
````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);
}
````
### Compatibility
One of the main goals of this project was to achieve maximum compatibility with CraftBukkit. And the end
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.
+137
View File
@@ -0,0 +1,137 @@
<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.tinyprotocol</groupId>
<artifactId>TinyProtocol</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>TinyProtocol</name>
<description>Intercept packets without ProtocolLib.</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib-Parent</artifactId>
<version>v4-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>dmulloy2-repo</id>
<url>http://repo.dmulloy2.net/content/groups/public/</url>
</repository>
</repositories>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
</archive>
<finalName>TinyProtocol</finalName>
<outputDirectory>../../target/</outputDirectory>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<phase>package</phase>
<id>attach-javadocs</id>
</execution>
</executions>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<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>
</project>
@@ -0,0 +1,117 @@
package com.comphenix.tinyprotocol;
import io.netty.channel.Channel;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.tinyprotocol.Reflection.ConstructorInvoker;
import com.comphenix.tinyprotocol.Reflection.FieldAccessor;
/**
* Represents an example plugin utilizing TinyProtocol
*/
public class ExamplePlugin extends JavaPlugin {
// Chat packets
private FieldAccessor<String> CHAT_MESSAGE = Reflection.getField("{nms}.PacketPlayInChat", String.class, 0);
// Explosion packet
private Class<?> particleClass = Reflection.getClass("{nms}.PacketPlayOutWorldParticles");
private FieldAccessor<String> particleName = Reflection.getField(particleClass, String.class, 0);
private FieldAccessor<Float> particleX = Reflection.getField(particleClass, float.class, 0);
private FieldAccessor<Float> particleY = Reflection.getField(particleClass, float.class, 1);
private FieldAccessor<Float> particleZ = Reflection.getField(particleClass, float.class, 2);
private FieldAccessor<Integer> particleCount = Reflection.getField(particleClass, int.class, 0);
// Server info packet
private Class<?> serverInfoClass = Reflection.getClass("{nms}.PacketStatusOutServerInfo");
private Class<Object> serverPingClass = Reflection.getUntypedClass("{nms}.ServerPing");
private Class<Object> playerSampleClass = Reflection.getUntypedClass("{nms}.ServerPingPlayerSample");
private FieldAccessor<Object> serverPing = Reflection.getField(serverInfoClass, serverPingClass, 0);
private FieldAccessor<Object> playerSample = Reflection.getField(serverPingClass, playerSampleClass, 0);
private ConstructorInvoker playerSampleInvoker = Reflection.getConstructor(playerSampleClass, int.class, int.class);
private TinyProtocol protocol;
@Override
public void onEnable() {
protocol = new TinyProtocol(this) {
@Override
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
// Cancel chat packets
if (CHAT_MESSAGE.hasField(packet)) {
if (CHAT_MESSAGE.get(packet).contains("dirty")) {
sendExplosion(sender);
return null;
}
}
if (particleName.hasField(packet)) {
System.out.println("Sending particle field:" + packet);
}
return super.onPacketInAsync(sender, channel, packet);
}
@Override
public Object onPacketOutAsync(Player reciever, Channel channel, Object packet) {
if (serverInfoClass.isInstance(packet)) {
Object ping = serverPing.get(packet);
playerSample.set(ping, playerSampleInvoker.invoke(1000, 0));
// Which is equivalent to:
// serverPing.get(packet).setPlayerSample(new ServerPingPlayerSample(1000, 0));
return packet;
}
return super.onPacketOutAsync(reciever, channel, packet);
}
};
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player) {
Player player = (Player) sender;
// Toggle injection
if (protocol.hasInjected(player)) {
protocol.uninjectPlayer(player);
sender.sendMessage(ChatColor.YELLOW + "Player " + player + " has been uninjected.");
} else {
protocol.injectPlayer(player);
sender.sendMessage(ChatColor.DARK_GREEN + "Player " + player + " has been injected.");
}
return true;
} else {
sender.sendMessage(ChatColor.RED + "Can only be invoked by a player.");
}
return false;
}
private void sendExplosion(Player player) {
try {
// Only visible for the client
Object explosionPacket = particleClass.newInstance();
Location loc = player.getLocation();
particleName.set(explosionPacket, "hugeexplosion");
particleX.set(explosionPacket, (float) loc.getX());
particleY.set(explosionPacket, (float) loc.getY());
particleZ.set(explosionPacket, (float) loc.getZ());
particleCount.set(explosionPacket, 1);
// Send the packet to the player
protocol.sendPacket(player, explosionPacket);
} catch (Exception e) {
throw new RuntimeException("Cannot send packet.", e);
}
}
}
@@ -0,0 +1,403 @@
package com.comphenix.tinyprotocol;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Bukkit;
/**
* An utility class that simplifies reflection in Bukkit plugins.
*
* @author Kristian
*/
public final class Reflection {
/**
* An interface for invoking a specific constructor.
*/
public interface ConstructorInvoker {
/**
* Invoke a constructor for a specific class.
*
* @param arguments - the arguments to pass to the constructor.
* @return The constructed object.
*/
public Object invoke(Object... arguments);
}
/**
* An interface for invoking a specific method.
*/
public interface MethodInvoker {
/**
* Invoke a method on a specific target object.
*
* @param target - the target object, or NULL for a static method.
* @param arguments - the arguments to pass to the method.
* @return The return value, or NULL if is void.
*/
public Object invoke(Object target, Object... arguments);
}
/**
* An interface for retrieving the field content.
*
* @param <T> - field type.
*/
public interface FieldAccessor<T> {
/**
* Retrieve the content of a field.
*
* @param target - the target object, or NULL for a static field.
* @return The value of the field.
*/
public T get(Object target);
/**
* Set the content of a field.
*
* @param target - the target object, or NULL for a static field.
* @param value - the new value of the field.
*/
public void set(Object target, Object value);
/**
* Determine if the given object has this field.
*
* @param target - the object to test.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasField(Object target);
}
// Deduce the net.minecraft.server.v* package
private static String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName();
private static String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server");
private static String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", "");
// Variable replacement
private static Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}");
private Reflection() {
// Seal class
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param target - the target type.
* @param name - the name of the field, or NULL to ignore.
* @param fieldType - a compatible field type.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) {
return getField(target, name, fieldType, 0);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param name - the name of the field, or NULL to ignore.
* @param fieldType - a compatible field type.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) {
return getField(getClass(className), name, fieldType, 0);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param target - the target type.
* @param fieldType - a compatible field type.
* @param index - the number of compatible fields to skip.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) {
return getField(target, null, fieldType, index);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param fieldType - a compatible field type.
* @param index - the number of compatible fields to skip.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {
return getField(getClass(className), fieldType, index);
}
// Common method
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index) {
for (final Field field : target.getDeclaredFields()) {
if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) {
field.setAccessible(true);
// A function for retrieving a specific field value
return new FieldAccessor<T>() {
@Override
@SuppressWarnings("unchecked")
public T get(Object target) {
try {
return (T) field.get(target);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
}
}
@Override
public void set(Object target, Object value) {
try {
field.set(target, value);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
}
}
@Override
public boolean hasField(Object target) {
// target instanceof DeclaringClass
return field.getDeclaringClass().isAssignableFrom(target.getClass());
}
};
}
}
// Search in parent classes
if (target.getSuperclass() != null)
return getField(target.getSuperclass(), name, fieldType, index);
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param methodName - the method name, or NULL to skip.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getMethod(String className, String methodName, Class<?>... params) {
return getTypedMethod(getClass(className), methodName, null, params);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param methodName - the method name, or NULL to skip.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getMethod(Class<?> clazz, String methodName, Class<?>... params) {
return getTypedMethod(clazz, methodName, null, params);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param methodName - the method name, or NULL to skip.
* @param returnType - the expected return type, or NULL to ignore.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) {
for (final Method method : clazz.getDeclaredMethods()) {
if ((methodName == null || method.getName().equals(methodName))
&& (returnType == null || method.getReturnType().equals(returnType))
&& Arrays.equals(method.getParameterTypes(), params)) {
method.setAccessible(true);
return new MethodInvoker() {
@Override
public Object invoke(Object target, Object... arguments) {
try {
return method.invoke(target, arguments);
} catch (Exception e) {
throw new RuntimeException("Cannot invoke method " + method, e);
}
}
};
}
}
// Search in every superclass
if (clazz.getSuperclass() != null)
return getMethod(clazz.getSuperclass(), methodName, params);
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params)));
}
/**
* Search for the first publically and privately defined constructor of the given name and parameter count.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param params - the expected parameters.
* @return An object that invokes this constructor.
* @throws IllegalStateException If we cannot find this method.
*/
public static ConstructorInvoker getConstructor(String className, Class<?>... params) {
return getConstructor(getClass(className), params);
}
/**
* Search for the first publically and privately defined constructor of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param params - the expected parameters.
* @return An object that invokes this constructor.
* @throws IllegalStateException If we cannot find this method.
*/
public static ConstructorInvoker getConstructor(Class<?> clazz, Class<?>... params) {
for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (Arrays.equals(constructor.getParameterTypes(), params)) {
constructor.setAccessible(true);
return new ConstructorInvoker() {
@Override
public Object invoke(Object... arguments) {
try {
return constructor.newInstance(arguments);
} catch (Exception e) {
throw new RuntimeException("Cannot invoke constructor " + constructor, e);
}
}
};
}
}
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
}
/**
* Retrieve a class from its full name, without knowing its type on compile time.
* <p>
* This is useful when looking up fields by a NMS or OBC type.
* <p>
*
* @see {@link #getClass()} for more information.
* @param lookupName - the class name with variables.
* @return The class.
*/
public static Class<Object> getUntypedClass(String lookupName) {
@SuppressWarnings({ "rawtypes", "unchecked" })
Class<Object> clazz = (Class) getClass(lookupName);
return clazz;
}
/**
* Retrieve a class from its full name.
* <p>
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
* <p>
* <table border="1">
* <tr>
* <th>Variable</th>
* <th>Content</th>
* </tr>
* <tr>
* <td>{nms}</td>
* <td>Actual package name of net.minecraft.server.VERSION</td>
* </tr>
* <tr>
* <td>{obc}</td>
* <td>Actual pacakge name of org.bukkit.craftbukkit.VERSION</td>
* </tr>
* <tr>
* <td>{version}</td>
* <td>The current Minecraft package VERSION, if any.</td>
* </tr>
* </table>
*
* @param lookupName - the class name with variables.
* @return The looked up class.
* @throws IllegalArgumentException If a variable or class could not be found.
*/
public static Class<?> getClass(String lookupName) {
return getCanonicalClass(expandVariables(lookupName));
}
/**
* Retrieve a class in the net.minecraft.server.VERSION.* package.
*
* @param name - the name of the class, excluding the package.
* @throws IllegalArgumentException If the class doesn't exist.
*/
public static Class<?> getMinecraftClass(String name) {
return getCanonicalClass(NMS_PREFIX + "." + name);
}
/**
* Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package.
*
* @param name - the name of the class, excluding the package.
* @throws IllegalArgumentException If the class doesn't exist.
*/
public static Class<?> getCraftBukkitClass(String name) {
return getCanonicalClass(OBC_PREFIX + "." + name);
}
/**
* Retrieve a class by its canonical name.
*
* @param canonicalName - the canonical name.
* @return The class.
*/
private static Class<?> getCanonicalClass(String canonicalName) {
try {
return Class.forName(canonicalName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Cannot find " + canonicalName, e);
}
}
/**
* Expand variables such as "{nms}" and "{obc}" to their corresponding packages.
*
* @param name - the full name of the class.
* @return The expanded string.
*/
private static String expandVariables(String name) {
StringBuffer output = new StringBuffer();
Matcher matcher = MATCH_VARIABLE.matcher(name);
while (matcher.find()) {
String variable = matcher.group(1);
String replacement = "";
// Expand all detected variables
if ("nms".equalsIgnoreCase(variable))
replacement = NMS_PREFIX;
else if ("obc".equalsIgnoreCase(variable))
replacement = OBC_PREFIX;
else if ("version".equalsIgnoreCase(variable))
replacement = VERSION;
else
throw new IllegalArgumentException("Unknown variable: " + variable);
// Assume the expanded variables are all packages, and append a dot
if (replacement.length() > 0 && matcher.end() < name.length() && name.charAt(matcher.end()) != '.')
replacement += ".";
matcher.appendReplacement(output, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(output);
return output.toString();
}
}
@@ -0,0 +1,522 @@
package com.comphenix.tinyprotocol;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import com.comphenix.tinyprotocol.Reflection.FieldAccessor;
import com.comphenix.tinyprotocol.Reflection.MethodInvoker;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.mojang.authlib.GameProfile;
/**
* Represents a very tiny alternative to ProtocolLib.
* <p>
* It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)!
*
* @author Kristian
*/
public abstract class TinyProtocol {
private static final AtomicInteger ID = new AtomicInteger(0);
// Used in order to lookup a channel
private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle");
private static final FieldAccessor<Object> getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class);
private static final FieldAccessor<Object> getManager = Reflection.getField("{nms}.PlayerConnection", "networkManager", Object.class);
private static final FieldAccessor<Channel> getChannel = Reflection.getField("{nms}.NetworkManager", Channel.class, 0);
// Looking up ServerConnection
private static final Class<Object> minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer");
private static final Class<Object> serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection");
private static final FieldAccessor<Object> getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0);
private static final FieldAccessor<Object> getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0);
private static final MethodInvoker getNetworkMarkers = Reflection.getTypedMethod(serverConnectionClass, null, List.class, serverConnectionClass);
// Packets we have to intercept
private static final Class<?> PACKET_LOGIN_IN_START = Reflection.getMinecraftClass("PacketLoginInStart");
private static final FieldAccessor<GameProfile> getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
// Speedup channel lookup
private Map<String, Channel> channelLookup = new MapMaker().weakValues().makeMap();
private Listener listener;
// Channels that have already been removed
private Set<Channel> uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().<Channel, Boolean>makeMap());
// List of network markers
private List<Object> networkManagers;
// Injected channel handlers
private List<Channel> serverChannels = Lists.newArrayList();
private ChannelInboundHandlerAdapter serverChannelHandler;
private ChannelInitializer<Channel> beginInitProtocol;
private ChannelInitializer<Channel> endInitProtocol;
// Current handler name
private String handlerName;
protected volatile boolean closed;
protected Plugin plugin;
/**
* Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients.
* <p>
* You can construct multiple instances per plugin.
*
* @param plugin - the plugin.
*/
public TinyProtocol(final Plugin plugin) {
this.plugin = plugin;
// Compute handler name
this.handlerName = getHandlerName();
// Prepare existing players
registerBukkitEvents();
try {
registerChannelHandler();
registerPlayers(plugin);
} catch (IllegalArgumentException ex) {
// Damn you, late bind
plugin.getLogger().info("[TinyProtocol] Delaying server channel injection due to late bind.");
new BukkitRunnable() {
@Override
public void run() {
registerChannelHandler();
registerPlayers(plugin);
plugin.getLogger().info("[TinyProtocol] Late bind injection successful.");
}
}.runTask(plugin);
}
}
private void createServerChannelHandler() {
// Handle connected channels
endInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
try {
// This can take a while, so we need to stop the main thread from interfering
synchronized (networkManagers) {
// Stop injecting channels
if (!closed) {
channel.eventLoop().submit(() -> injectChannelInternal(channel));
}
}
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e);
}
}
};
// This is executed before Minecraft's channel handler
beginInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(endInitProtocol);
}
};
serverChannelHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = (Channel) msg;
// Prepare to initialize ths channel
channel.pipeline().addFirst(beginInitProtocol);
ctx.fireChannelRead(msg);
}
};
}
/**
* Register bukkit events.
*/
private void registerBukkitEvents() {
listener = new Listener() {
@EventHandler(priority = EventPriority.LOWEST)
public final void onPlayerLogin(PlayerLoginEvent e) {
if (closed)
return;
Channel channel = getChannel(e.getPlayer());
// Don't inject players that have been explicitly uninjected
if (!uninjectedChannels.contains(channel)) {
injectPlayer(e.getPlayer());
}
}
@EventHandler
public final void onPluginDisable(PluginDisableEvent e) {
if (e.getPlugin().equals(plugin)) {
close();
}
}
};
plugin.getServer().getPluginManager().registerEvents(listener, plugin);
}
@SuppressWarnings("unchecked")
private void registerChannelHandler() {
Object mcServer = getMinecraftServer.get(Bukkit.getServer());
Object serverConnection = getServerConnection.get(mcServer);
boolean looking = true;
// We need to synchronize against this list
networkManagers = (List<Object>) getNetworkMarkers.invoke(null, serverConnection);
createServerChannelHandler();
// Find the correct list, or implicitly throw an exception
for (int i = 0; looking; i++) {
List<Object> list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection);
for (Object item : list) {
if (!ChannelFuture.class.isInstance(item))
break;
// Channel future that contains the server connection
Channel serverChannel = ((ChannelFuture) item).channel();
serverChannels.add(serverChannel);
serverChannel.pipeline().addFirst(serverChannelHandler);
looking = false;
}
}
}
private void unregisterChannelHandler() {
if (serverChannelHandler == null)
return;
for (Channel serverChannel : serverChannels) {
final ChannelPipeline pipeline = serverChannel.pipeline();
// Remove channel handler
serverChannel.eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
pipeline.remove(serverChannelHandler);
} catch (NoSuchElementException e) {
// That's fine
}
}
});
}
}
private void registerPlayers(Plugin plugin) {
for (Player player : plugin.getServer().getOnlinePlayers()) {
injectPlayer(player);
}
}
/**
* Invoked when the server is starting to send a packet to a player.
* <p>
* Note that this is not executed on the main thread.
*
* @param receiver - the receiving player, NULL for early login/status packets.
* @param channel - the channel that received the packet. Never NULL.
* @param packet - the packet being sent.
* @return The packet to send instead, or NULL to cancel the transmission.
*/
public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) {
return packet;
}
/**
* Invoked when the server has received a packet from a given player.
* <p>
* Use {@link Channel#remoteAddress()} to get the remote address of the client.
*
* @param sender - the player that sent the packet, NULL for early login/status packets.
* @param channel - channel that received the packet. Never NULL.
* @param packet - the packet being received.
* @return The packet to recieve instead, or NULL to cancel.
*/
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
return packet;
}
/**
* Send a packet to a particular player.
* <p>
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param player - the destination player.
* @param packet - the packet to send.
*/
public void sendPacket(Player player, Object packet) {
sendPacket(getChannel(player), packet);
}
/**
* Send a packet to a particular client.
* <p>
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet to send.
*/
public void sendPacket(Channel channel, Object packet) {
channel.pipeline().writeAndFlush(packet);
}
/**
* Pretend that a given packet has been received from a player.
* <p>
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param player - the player that sent the packet.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Player player, Object packet) {
receivePacket(getChannel(player), packet);
}
/**
* Pretend that a given packet has been received from a given client.
* <p>
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Channel channel, Object packet) {
channel.pipeline().context("encoder").fireChannelRead(packet);
}
/**
* Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID.
* <p>
* Note that this method will only be invoked once. It is no longer necessary to override this to support multiple instances.
*
* @return A unique channel handler name.
*/
protected String getHandlerName() {
return "tiny-" + plugin.getName() + "-" + ID.incrementAndGet();
}
/**
* Add a custom channel handler to the given player's channel pipeline, allowing us to intercept sent and received packets.
* <p>
* This will automatically be called when a player has logged in.
*
* @param player - the player to inject.
*/
public void injectPlayer(Player player) {
injectChannelInternal(getChannel(player)).player = player;
}
/**
* Add a custom channel handler to the given channel.
*
* @param channel - the channel to inject.
* @return The intercepted channel, or NULL if it has already been injected.
*/
public void injectChannel(Channel channel) {
injectChannelInternal(channel);
}
/**
* Add a custom channel handler to the given channel.
*
* @param channel - the channel to inject.
* @return The packet interceptor.
*/
private PacketInterceptor injectChannelInternal(Channel channel) {
try {
PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName);
// Inject our packet interceptor
if (interceptor == null) {
interceptor = new PacketInterceptor();
channel.pipeline().addBefore("packet_handler", handlerName, interceptor);
uninjectedChannels.remove(channel);
}
return interceptor;
} catch (IllegalArgumentException e) {
// Try again
return (PacketInterceptor) channel.pipeline().get(handlerName);
}
}
/**
* Retrieve the Netty channel associated with a player. This is cached.
*
* @param player - the player.
* @return The Netty channel.
*/
public Channel getChannel(Player player) {
Channel channel = channelLookup.get(player.getName());
// Lookup channel again
if (channel == null) {
Object connection = getConnection.get(getPlayerHandle.invoke(player));
Object manager = getManager.get(connection);
channelLookup.put(player.getName(), channel = getChannel.get(manager));
}
return channel;
}
/**
* Uninject a specific player.
*
* @param player - the injected player.
*/
public void uninjectPlayer(Player player) {
uninjectChannel(getChannel(player));
}
/**
* Uninject a specific channel.
* <p>
* This will also disable the automatic channel injection that occurs when a player has properly logged in.
*
* @param channel - the injected channel.
*/
public void uninjectChannel(final Channel channel) {
// No need to guard against this if we're closing
if (!closed) {
uninjectedChannels.add(channel);
}
// See ChannelInjector in ProtocolLib, line 590
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
channel.pipeline().remove(handlerName);
}
});
}
/**
* Determine if the given player has been injected by TinyProtocol.
*
* @param player - the player.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean hasInjected(Player player) {
return hasInjected(getChannel(player));
}
/**
* Determine if the given channel has been injected by TinyProtocol.
*
* @param channel - the channel.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean hasInjected(Channel channel) {
return channel.pipeline().get(handlerName) != null;
}
/**
* Cease listening for packets. This is called automatically when your plugin is disabled.
*/
public final void close() {
if (!closed) {
closed = true;
// Remove our handlers
for (Player player : plugin.getServer().getOnlinePlayers()) {
uninjectPlayer(player);
}
// Clean up Bukkit
HandlerList.unregisterAll(listener);
unregisterChannelHandler();
}
}
/**
* Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
*
* @author Kristian
*/
private final class PacketInterceptor extends ChannelDuplexHandler {
// Updated by the login event
public volatile Player player;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// Intercept channel
final Channel channel = ctx.channel();
handleLoginStart(channel, msg);
try {
msg = onPacketInAsync(player, channel, msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e);
}
if (msg != null) {
super.channelRead(ctx, msg);
}
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
try {
msg = onPacketOutAsync(player, ctx.channel(), msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
}
if (msg != null) {
super.write(ctx, msg, promise);
}
}
private void handleLoginStart(Channel channel, Object packet) {
if (PACKET_LOGIN_IN_START.isInstance(packet)) {
GameProfile profile = getGameProfile.get(packet);
channelLookup.put(profile.getName(), channel);
}
}
}
}
@@ -0,0 +1,14 @@
name: TinyProtocol
version: 0.1.0
description: Intercept packets without ProtocolLib
author: Comphenix
load: startup
main: com.comphenix.tinyprotocol.ExamplePlugin
database: false
commands:
toggleinjection:
description: Toggle the TinyProtocol injection of the current player.
usage: /<command>
+335
View File
@@ -0,0 +1,335 @@
<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>
<name>ProtocolLib</name>
<version>4.5.0</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>https://github.com/dmulloy2/ProtocolLib</url>
<packaging>jar</packaging>
<inceptionYear>2012</inceptionYear>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.number></project.build.number>
<project.fullVersion>${project.version}</project.fullVersion>
<powermock.version>2.0.4</powermock.version>
<spigot.version>1.15.1-R0.1-SNAPSHOT</spigot.version>
</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>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.sf</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.spigotmc:spigot</exclude>
<exclude>org.spigotmc:spigot-api</exclude>
<exclude>junit:junit</exclude>
<exclude>io.netty:*</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</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>${project.name}</finalName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M4</version>
<configuration>
<systemProperties>
<property>
<name>projectVersion</name>
<value>${project.version}</value>
</property>
</systemProperties>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<failOnError>false</failOnError>
<encoding>ISO-8859-1</encoding>
<doctitle>ProtocolLib JavaDocs</doctitle>
<windowtitle>ProtocolLib JavaDocs</windowtitle>
<bottom>Copyright &#169; {inceptionYear}&#x2013;{currentYear}
Comphenix and dmulloy2. Licensed under the GNU GPL v2.</bottom>
<additionalOptions>
<additionalOption>-Xdoclint:none</additionalOption>
</additionalOptions>
</configuration>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>aggregate</goal>
</goals>
</execution>
</executions>
</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>-->
<project.fullVersion>${project.version}</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.2.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>3.1.1</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>
<distributionManagement>
<repository>
<id>dmulloy2-releases</id>
<url>https://repo.dmulloy2.net/nexus/repository/releases/</url>
</repository>
<snapshotRepository>
<id>dmulloy2-snapshots</id>
<url>https://repo.dmulloy2.net/nexus/repository/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<!-- Compile with the old version of Netty for backwards compat -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.23.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.5</version>
<scope>compile</scope>
</dependency>
<!-- TODO fold BukkitExecutors into ProtocolLib -->
<dependency>
<groupId>com.comphenix.executors</groupId>
<artifactId>BukkitExecutors</artifactId>
<version>1.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- Testing dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,155 @@
/*
* 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.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;
/**
* Represents a asynchronous packet handler.
*
* @author Kristian
*/
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 receive these asynchronous events.
* @return An asynchronous handler.
*/
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
/**
* Unregisters and closes the given asynchronous handler.
* @param handler - asynchronous handler.
*/
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.
*/
public void unregisterAsyncHandlers(Plugin plugin);
/**
* 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.
* @param packet - packet to test.
* @return TRUE if it does, FALSE otherwise.
*/
public abstract boolean hasAsynchronousListeners(PacketEvent packet);
/**
* Retrieve the default packet stream.
* @return Default packet stream.
*/
public abstract PacketStream getPacketStream();
/**
* Retrieve the default error reporter.
* @return Default reporter.
*/
public abstract ErrorReporter getErrorReporter();
/**
* Remove listeners, close threads and transmit every delayed packet.
*/
public abstract void cleanupAll();
/**
* Signal that a packet is ready to be transmitted.
* <p>
* This should only be called if {@link com.comphenix.protocol.async.AsyncMarker#incrementProcessingDelay() AsyncMarker.incrementProcessingDelay()}
* has been called previously.
* @param packet - packet to signal.
*/
public abstract void signalPacketTransmission(PacketEvent packet);
/**
* Register a synchronous listener that handles packets when they time out.
* @param listener - synchronous listener that will handle timed out packets.
*/
public abstract void registerTimeoutHandler(PacketListener listener);
/**
* Unregisters a given timeout listener.
* @param listener - the timeout listener to unregister.
*/
public abstract void unregisterTimeoutHandler(PacketListener listener);
/**
* Get a immutable set of every registered timeout handler.
* @return Set of every registered timeout handler.
*/
public abstract Set<PacketListener> getTimeoutHandlers();
/**
* Get an immutable set of every registered asynchronous packet listener.
* @return Set of every asynchronous packet listener.
*/
public abstract Set<PacketListener> getAsyncHandlers();
}
@@ -0,0 +1,170 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import com.comphenix.protocol.async.AsyncListenerHandler;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodUtils;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.compiler.StructureCompiler;
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
/**
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
*
* @author Kristian
*/
class CleanupStaticMembers {
// Reports
public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
private ClassLoader loader;
private ErrorReporter reporter;
public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) {
this.loader = loader;
this.reporter = reporter;
}
/**
* Ensure that the previous ClassLoader is not leaking.
*/
public void resetAll() {
// This list must always be updated
@SuppressWarnings("deprecation")
Class<?>[] publicClasses = {
AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class,
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
BackgroundCompiler.class, StructureCompiler.class,
ObjectWriter.class, Packets.Server.class, Packets.Client.class,
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class,
AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class,
MinecraftReflection.class, NbtBinarySerializer.class
};
String[] internalClasses = {
"com.comphenix.protocol.events.SerializedOfflinePlayer",
"com.comphenix.protocol.injector.player.InjectedServerConnection",
"com.comphenix.protocol.injector.player.NetworkFieldInjector",
"com.comphenix.protocol.injector.player.NetworkObjectInjector",
"com.comphenix.protocol.injector.player.NetworkServerInjector",
"com.comphenix.protocol.injector.player.PlayerInjector",
"com.comphenix.protocol.injector.EntityUtilities",
"com.comphenix.protocol.injector.packet.PacketRegistry",
"com.comphenix.protocol.injector.packet.PacketInjector",
"com.comphenix.protocol.injector.packet.ReadPacketModifier",
"com.comphenix.protocol.injector.StructureCache",
"com.comphenix.protocol.reflect.compiler.BoxingHelper",
"com.comphenix.protocol.reflect.compiler.MethodDescriptor",
"com.comphenix.protocol.wrappers.nbt.WrappedElement",
};
resetClasses(publicClasses);
resetClasses(getClasses(loader, internalClasses));
}
private void resetClasses(Class<?>[] classes) {
// Reset each class one by one
for (Class<?> clazz : classes) {
resetClass(clazz);
}
}
private void resetClass(Class<?> clazz) {
for (Field field : clazz.getFields()) {
Class<?> type = field.getType();
// Only check static non-primitive fields. We also skip strings.
if (Modifier.isStatic(field.getModifiers()) &&
!type.isPrimitive() && !type.equals(String.class) &&
!type.equals(ReportType.class)) {
try {
setFinalStatic(field, null);
} catch (IllegalAccessException e) {
// Just inform the player
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
);
e.printStackTrace();
}
}
}
}
// HACK! HAACK!
private static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException {
int modifier = field.getModifiers();
boolean isFinal = Modifier.isFinal(modifier);
Field modifiersField = isFinal ? FieldUtils.getField(Field.class, "modifiers", true) : null;
// We have to remove the final field first
if (isFinal) {
FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true);
}
// Now we can safely modify the field
FieldUtils.writeStaticField(field, newValue, true);
// Revert modifier
if (isFinal) {
FieldUtils.writeField(modifiersField, field, modifier, true);
}
}
private Class<?>[] getClasses(ClassLoader loader, String[] names) {
List<Class<?>> output = new ArrayList<Class<?>>();
for (String name : names) {
try {
output.add(loader.loadClass(name));
} catch (ClassNotFoundException e) {
// Warn the user
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_UNLOAD_CLASS).error(e).messageParam(name));
}
}
return output.toArray(new Class<?>[0]);
}
}
@@ -0,0 +1,156 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
/**
* Base class for all our commands.
*
* @author Kristian
*/
abstract class CommandBase implements CommandExecutor {
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
public static final String PERMISSION_ADMIN = "protocol.admin";
private String permission;
private String name;
private int minimumArgumentCount;
protected ErrorReporter reporter;
public CommandBase(ErrorReporter reporter, String permission, String name) {
this(reporter, permission, name, 0);
}
public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) {
this.reporter = reporter;
this.name = name;
this.permission = permission;
this.minimumArgumentCount = minimumArgumentCount;
}
@Override
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
try {
// Make sure we're dealing with the correct command
if (!command.getName().equalsIgnoreCase(name)) {
reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
return false;
}
if (permission != null && !sender.hasPermission(permission)) {
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
return true;
}
// Check argument length
if (args != null && args.length >= minimumArgumentCount) {
return handleCommand(sender, args);
} else {
sender.sendMessage(ChatColor.RED + "Insufficient arguments. You need at least " + minimumArgumentCount);
return false;
}
} catch (Throwable ex) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_COMMAND_ERROR).error(ex).messageParam(name).callerParam(sender, label, args)
);
return true;
}
}
/**
* Parse a boolean value at the head of the queue.
* @param arguments - the queue of arguments.
* @param parameterName - the parameter name we will match.
* @return The parsed boolean, or NULL if not valid.
*/
protected Boolean parseBoolean(Deque<String> arguments, String parameterName) {
Boolean result = null;
if (!arguments.isEmpty()) {
String arg = arguments.peek();
if (arg.equalsIgnoreCase("true") || arg.equalsIgnoreCase("on"))
result = true;
else if (arg.equalsIgnoreCase(parameterName))
result = true;
else if (arg.equalsIgnoreCase("false") || arg.equalsIgnoreCase("off"))
result = false;
}
if (result != null)
arguments.poll();
return result;
}
/**
* Create a queue from a sublist of a given array.
* @param args - the source array.
* @param start - the starting index.
* @return A queue that contains every element in the array, starting at the given index.
*/
protected Deque<String> toQueue(String[] args, int start) {
return new ArrayDeque<String>(Arrays.asList(args).subList(start, args.length));
}
/**
* Retrieve the permission necessary to execute this command.
* @return The permission, or NULL if not needed.
*/
public String getPermission() {
return permission;
}
/**
* Retrieve the primary name of this command.
* @return Primary name.
*/
public String getName() {
return name;
}
/**
* Retrieve the error reporter.
* @return Error reporter.
*/
protected ErrorReporter getReporter() {
return reporter;
}
/**
* Main implementation of this command.
* @param sender - command sender.
* @param args - input arguments.
* @return TRUE if the command was successfully handled, FALSE otherwise.
*/
protected abstract boolean handleCommand(CommandSender sender, String[] args);
}
@@ -0,0 +1,481 @@
package com.comphenix.protocol;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.conversations.Conversable;
import org.bukkit.conversations.Conversation;
import org.bukkit.conversations.ConversationAbandonedEvent;
import org.bukkit.conversations.ConversationAbandonedListener;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.ConversationFactory;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.MultipleLinesPrompt.MultipleConversationCanceller;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.Sets;
/**
* A command to apply JavaScript filtering to the packet command.
*
* @author Kristian
*/
public class CommandFilter extends CommandBase {
public static final ReportType REPORT_FALLBACK_ENGINE = new ReportType("Falling back to the Rhino engine.");
public static final ReportType REPORT_CANNOT_LOAD_FALLBACK_ENGINE = new ReportType("Could not load Rhino either. Please upgrade your JVM or OS.");
public static final ReportType REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE = new ReportType("Unable to initialize packages for JavaScript engine.");
public static final ReportType REPORT_FILTER_REMOVED_FOR_ERROR = new ReportType("Removing filter %s for causing %s.");
public static final ReportType REPORT_CANNOT_HANDLE_CONVERSATION = new ReportType("Cannot handle conversation.");
public interface FilterFailedHandler{
/**
* Invoked when a given filter has failed.
* @param event - the packet event.
* @param filter - the filter that failed.
* @param ex - the failure.
* @return TRUE to keep processing this filter, FALSE to remove it.
*/
public boolean handle(PacketEvent event, Filter filter, Exception ex);
}
/**
* Possible sub commands.
*
* @author Kristian
*/
private enum SubCommand {
ADD, REMOVE;
}
/**
* A filter that will be used to process a packet event.
* @author Kristian
*/
public static class Filter {
private final String name;
private final String predicate;
private final Set<PacketType> packets;
/**
* Construct a new immutable filter.
* @param name - the unique name of the filter.
* @param predicate - the JavaScript predicate that will be used to filter packet events.
* @param packets - a list of packet types this filter applies to.
*/
public Filter(String name, String predicate, Set<PacketType> packets) {
this.name = name;
this.predicate = predicate;
this.packets = Sets.newHashSet(packets);
}
/**
* Retrieve the unique name of the filter.
* @return Unique name of the filter.
*/
public String getName() {
return name;
}
/**
* Retrieve the JavaScript predicate that will be used to filter packet events.
* @return Predicate itself.
*/
public String getPredicate() {
return predicate;
}
/**
* Retrieve a copy of the set of packets this filter applies to.
* @return Set of packets this filter applies to.
*/
public Set<PacketType> getRanges() {
return Sets.newHashSet(packets);
}
/**
* Determine whether or not a packet event needs to be passed to this filter.
* @param event - the event to test.
* @return TRUE if it does, FALSE otherwise.
*/
private boolean isApplicable(PacketEvent event) {
return packets.contains(event.getPacketType());
}
/**
* Evaluate the current filter using the provided ScriptEngine as context.
* <p>
* This context may be modified with additional code.
* @param context - the current script context.
* @param event - the packet event to evaluate.
* @return TRUE to pass this packet event on to the debug listeners, FALSE otherwise.
* @throws ScriptException If the compilation failed or the filter is not valid.
*/
public boolean evaluate(ScriptEngine context, PacketEvent event) throws ScriptException {
if (!isApplicable(event))
return true;
// Ensure that the predicate has been compiled
compile(context);
try {
Object result = ((Invocable) context).invokeFunction(name, event, event.getPacket().getHandle());
if (result instanceof Boolean)
return (Boolean) result;
else
throw new ScriptException("Filter result wasn't a boolean: " + result);
} catch (NoSuchMethodException e) {
// Must be a fault with the script engine itself
throw new IllegalStateException("Unable to compile " + name + " into current script engine.", e);
}
}
/**
* Force the compilation of a specific filter.
* @param context - the current script context.
* @throws ScriptException If the compilation failed.
*/
public void compile(ScriptEngine context) throws ScriptException {
if (context.get(name) == null) {
context.eval("var " + name + " = function(event, packet) {\n" + predicate);
}
}
/**
* Clean up all associated code from this filter in the provided script engine.
* @param context - the current script context.
*/
public void close(ScriptEngine context) {
context.put(name, null);
}
}
private class CompilationSuccessCanceller implements MultipleConversationCanceller {
@Override
public boolean cancelBasedOnInput(ConversationContext context, String in) {
throw new UnsupportedOperationException("Cannot cancel on the last line alone.");
}
@Override
public void setConversation(Conversation conversation) {
// Ignore
}
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine, StringBuilder lines, int lineCount) {
try {
engine.eval("function(event, packet) {\n" + lines.toString());
// It compiles - accept the filter!
return true;
} catch (ScriptException e) {
// We also have the function() line
int realLineCount = lineCount + 1;
// Only possible to recover from an error on the last line.
return e.getLineNumber() < realLineCount;
}
}
@Override
public CompilationSuccessCanceller clone() {
return new CompilationSuccessCanceller();
}
}
/**
* Name of this command.
*/
public static final String NAME = "filter";
// Default error handler
private FilterFailedHandler defaultFailedHandler;
// Currently registered filters
private List<Filter> filters = new ArrayList<Filter>();
// Owner plugin
private final Plugin plugin;
// Whether or not the command is enabled
private ProtocolConfig config;
// Script engine
private ScriptEngine engine;
private boolean uninitialized;
public CommandFilter(ErrorReporter reporter, Plugin plugin, ProtocolConfig config) {
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 2);
this.plugin = plugin;
this.config = config;
// Tell the filter system to initialize the script first chance it gets
this.uninitialized = true;
}
private void initalizeScript() {
try {
// First attempt
initializeEngine();
// Oh for ..
if (!isInitialized()) {
throw new ScriptException("A JavaScript engine could not be found.");
} else {
plugin.getLogger().info("Loaded command filter engine.");
}
} catch (ScriptException e1) {
// It's not a huge deal
printPackageWarning(e1);
if (!config.getScriptEngineName().equals("rhino")) {
reporter.reportWarning(this, Report.newBuilder(REPORT_FALLBACK_ENGINE));
config.setScriptEngineName("rhino");
config.saveAll();
try {
initializeEngine();
if (!isInitialized()) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_FALLBACK_ENGINE));
}
} catch (ScriptException e2) {
// And again ..
printPackageWarning(e2);
}
}
}
}
private void printPackageWarning(ScriptException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE).error(e));
}
/**
* Initialize the current configured engine.
* @throws ScriptException If we are unable to import packages.
*/
private void initializeEngine() throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
engine = manager.getEngineByName(config.getScriptEngineName());
// Import useful packages
if (engine != null) {
engine.eval("importPackage(org.bukkit);");
engine.eval("importPackage(com.comphenix.protocol.reflect);");
}
}
/**
* Determine if the filter engine has been successfully initialized.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isInitialized() {
return engine != null;
}
private FilterFailedHandler getDefaultErrorHandler() {
// No need to create a new object every time
if (defaultFailedHandler == null) {
defaultFailedHandler = new FilterFailedHandler() {
@Override
public boolean handle(PacketEvent event, Filter filter, Exception ex) {
reporter.reportMinimal(plugin, "filterEvent(PacketEvent)", ex, event);
reporter.reportWarning(this,
Report.newBuilder(REPORT_FILTER_REMOVED_FOR_ERROR).messageParam(filter.getName(), ex.getClass().getSimpleName())
);
return false;
}
};
}
return defaultFailedHandler;
}
/**
* Determine whether or not to pass the given packet event to the packet listeners.
* <p>
* Uses a default filter failure handler that simply prints the error message and removes the filter.
* @param event - the event.
* @return TRUE if we should, FALSE otherwise.
*/
public boolean filterEvent(PacketEvent event) {
return filterEvent(event, getDefaultErrorHandler());
}
/**
* Determine whether or not to pass the given packet event to the packet listeners.
* @param event - the event.
* @param handler - failure handler.
* @return TRUE if we should, FALSE otherwise.
*/
public boolean filterEvent(PacketEvent event, FilterFailedHandler handler) {
for (Iterator<Filter> it = filters.iterator(); it.hasNext(); ) {
Filter filter = it.next();
try {
if (!filter.evaluate(engine, event)) {
return false;
}
} catch (Exception ex) {
if (!handler.handle(event, filter, ex)) {
it.remove();
}
}
}
// Pass!
return true;
}
/**
* Initialize the script engine if necessary.
*/
private void checkScriptStatus() {
// Start the engine
if (uninitialized) {
uninitialized = false;
initalizeScript();
}
}
/*
* Description: Adds or removes a simple packet filter.
Usage: /<command> add|remove name [packet IDs]
*/
@Override
protected boolean handleCommand(CommandSender sender, String[] args) {
checkScriptStatus();
if (!config.isDebug()) {
sender.sendMessage(ChatColor.RED + "Debug mode must be enabled in the configuration first!");
return true;
}
if (!isInitialized()) {
sender.sendMessage(ChatColor.RED + "JavaScript engine was not present. Filter system is disabled.");
return true;
}
final SubCommand command = parseCommand(args, 0);
final String name = args[1];
switch (command) {
case ADD:
// Never overwrite an existing filter
if (findFilter(name) != null) {
sender.sendMessage(ChatColor.RED + "Filter " + name + " already exists. Remove it first.");
return true;
}
// Prepare the input to the packet type parser
Deque<String> rangeArguments = toQueue(args, 2);
final PacketTypeParser parser = new PacketTypeParser();
final Set<PacketType> packets = parser.parseTypes(rangeArguments, PacketTypeParser.DEFAULT_MAX_RANGE);
sender.sendMessage("Enter filter program ('}' to complete or CANCEL):");
// Make sure we can use the conversable interface
if (sender instanceof Conversable) {
final MultipleLinesPrompt prompt =
new MultipleLinesPrompt(new CompilationSuccessCanceller(), "function(event, packet) {");
new ConversationFactory(plugin).
withFirstPrompt(prompt).
withEscapeSequence("CANCEL").
withLocalEcho(false).
addConversationAbandonedListener(new ConversationAbandonedListener() {
@Override
public void conversationAbandoned(ConversationAbandonedEvent event) {
try {
final Conversable whom = event.getContext().getForWhom();
if (event.gracefulExit()) {
String predicate = prompt.removeAccumulatedInput(event.getContext());
Filter filter = new Filter(name, predicate, packets);
// Print the last line as well
whom.sendRawMessage(prompt.getPromptText(event.getContext()));
try {
// Force early compilation
filter.compile(engine);
filters.add(filter);
whom.sendRawMessage(ChatColor.GOLD + "Added filter " + name);
} catch (ScriptException e) {
e.printStackTrace();
whom.sendRawMessage(ChatColor.GOLD + "Compilation error: " + e.getMessage());
}
} else {
// Too bad
whom.sendRawMessage(ChatColor.RED + "Cancelled filter.");
}
} catch (Exception e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HANDLE_CONVERSATION).error(e).callerParam(event)
);
}
}
}).
buildConversation((Conversable) sender).
begin();
} else {
sender.sendMessage(ChatColor.RED + "Only console and players are supported!");
}
break;
case REMOVE:
Filter filter = findFilter(name);
// See if it exists before we remove it
if (filter != null) {
filter.close(engine);
filters.remove(filter);
sender.sendMessage(ChatColor.GOLD + "Removed filter " + name);
} else {
sender.sendMessage(ChatColor.RED + "Unable to find a filter by the name " + name);
}
break;
}
return true;
}
/**
* Lookup a filter by its name.
* @param name - the filter name.
* @return The filter, or NULL if not found.
*/
private Filter findFilter(String name) {
// We'll just use a linear scan for now - we don't expect that many filters
for (Filter filter : filters) {
if (filter.getName().equalsIgnoreCase(name)) {
return filter;
}
}
return null;
}
private SubCommand parseCommand(String[] args, int index) {
String text = args[index].toUpperCase();
try {
return SubCommand.valueOf(text);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.", e);
}
}
}
@@ -0,0 +1,547 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.cglib.proxy.Factory;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.PrettyPrinter;
import com.comphenix.protocol.reflect.PrettyPrinter.ObjectPrinter;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.HexDumper;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;
/**
* Handles the "packet" debug command.
*
* @author Kristian
*/
class CommandPacket extends CommandBase {
public static final ReportType REPORT_CANNOT_SEND_MESSAGE = new ReportType("Cannot send chat message.");
private enum SubCommand {
ADD, REMOVE, NAMES, PAGE;
}
/**
* Name of this command.
*/
public static final String NAME = "packet";
/**
* Number of lines per page.
*/
public static final int PAGE_LINE_COUNT = 9;
/**
* Number of bytes before we do a hex dump.
*/
private static final int HEX_DUMP_THRESHOLD = 256;
private Plugin plugin;
private Logger logger;
private ProtocolManager manager;
private ChatExtensions chatter;
// The main parser
private PacketTypeParser typeParser = new PacketTypeParser();
// Paged message
private Map<CommandSender, List<String>> pagedMessage = new WeakHashMap<CommandSender, List<String>>();
// Current registered packet types
private PacketTypeSet packetTypes = new PacketTypeSet();
private PacketTypeSet extendedTypes = new PacketTypeSet();
// Compare listeners
private PacketTypeSet compareTypes = new PacketTypeSet();
private Map<PacketEvent, String> originalPackets = new MapMaker().weakKeys().makeMap();
// The packet listener
private PacketListener listener;
// Compare listener
private PacketListener compareListener;
// Filter packet events
private CommandFilter filter;
public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, CommandFilter filter, ProtocolManager manager) {
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
this.plugin = plugin;
this.logger = logger;
this.manager = manager;
this.filter = filter;
this.chatter = new ChatExtensions(manager);
}
/**
* Send a message without invoking the packet listeners.
* @param receiver - the player to send it to.
* @param message - the message to send.
* @return TRUE if the message was sent successfully, FALSE otherwise.
*/
public void sendMessageSilently(CommandSender receiver, String message) {
try {
chatter.sendMessageSilently(receiver, message);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(receiver, message)
);
}
}
/**
* Broadcast a message without invoking any packet listeners.
* @param message - message to send.
* @param permission - permission required to receieve the message. NULL to target everyone.
*/
public void broadcastMessageSilently(String message, String permission) {
try {
chatter.broadcastMessageSilently(message, permission);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(message, permission)
);
}
}
private void printPage(CommandSender sender, int pageIndex) {
List<String> paged = pagedMessage.get(sender);
// Make sure the player has any pages
if (paged != null) {
int lastPage = ((paged.size() - 1) / PAGE_LINE_COUNT) + 1;
for (int i = PAGE_LINE_COUNT * (pageIndex - 1); i < PAGE_LINE_COUNT * pageIndex; i++) {
if (i < paged.size()) {
sendMessageSilently(sender, " " + paged.get(i));
}
}
// More data?
if (pageIndex < lastPage) {
sendMessageSilently(sender, "Send /packet page " + (pageIndex + 1) + " for the next page.");
}
} else {
sendMessageSilently(sender, ChatColor.RED + "No pages found.");
}
}
/*
* Description: Adds or removes a simple packet listener.
Usage: /<command> add|remove client|server|both [ID start] [ID stop] [detailed]
*/
@Override
protected boolean handleCommand(CommandSender sender, String[] args) {
try {
Deque<String> arguments = new ArrayDeque<String>(Arrays.asList(args));
SubCommand subCommand = parseCommand(arguments);
// Commands with different parameters
if (subCommand == SubCommand.PAGE) {
if (args.length <= 1) {
sendMessageSilently(sender, ChatColor.RED + "Must specify a page index.");
return true;
}
int page = Integer.parseInt(args[1]);
if (page > 0)
printPage(sender, page);
else
sendMessageSilently(sender, ChatColor.RED + "Page index must be greater than zero.");
return true;
}
Set<PacketType> types = typeParser.parseTypes(arguments, PacketTypeParser.DEFAULT_MAX_RANGE);
Boolean detailed = parseBoolean(arguments, "detailed");
Boolean compare = parseBoolean(arguments, "compare");
// Notify user
if (typeParser.getLastProtocol() == null) {
sender.sendMessage(ChatColor.YELLOW + "Warning: Missing protocol (PLAY, etc) - assuming legacy IDs.");
}
if (arguments.size() > 0) {
throw new IllegalArgumentException("Cannot parse " + arguments);
}
// The last elements are optional
if (detailed == null) {
detailed = false;
}
if (compare == null) {
compare = false;
} else {
// This is implied - we cannot compare content without going detailed
detailed = true;
}
// Perform commands
if (subCommand == SubCommand.ADD) {
// The add command is dangerous - don't default on the connection side
if (args.length == 1) {
sender.sendMessage(ChatColor.RED + "Please specify a connection side.");
return false;
}
executeAddCommand(sender, types, detailed, compare);
} else if (subCommand == SubCommand.REMOVE) {
executeRemoveCommand(sender, types);
} else if (subCommand == SubCommand.NAMES) {
executeNamesCommand(sender, types);
}
} catch (NumberFormatException e) {
sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage());
} catch (IllegalArgumentException e) {
sendMessageSilently(sender, ChatColor.RED + e.getMessage());
}
return true;
}
private void executeAddCommand(CommandSender sender, Set<PacketType> addition, boolean detailed, boolean compare) {
packetTypes.addAll(addition);
// Also mark these types as "detailed"
if (detailed) {
extendedTypes.addAll(addition);
}
// Whether or not to compare the packet with the initial state
if (compare) {
compareTypes.addAll(addition);
}
updatePacketListener();
sendMessageSilently(sender, ChatColor.YELLOW + "Added listener " + getWhitelistInfo(listener));
}
private void executeRemoveCommand(CommandSender sender, Set<PacketType> removal) {
packetTypes.removeAll(removal);
extendedTypes.removeAll(removal);
compareTypes.removeAll(removal);
updatePacketListener();
sendMessageSilently(sender, ChatColor.YELLOW + "Removing packet types.");
}
private void executeNamesCommand(CommandSender sender, Set<PacketType> types) {
List<String> messages = new ArrayList<String>();
// Print the equivalent name of every given ID
for (PacketType type : types) {
messages.add(ChatColor.YELLOW + type.toString());
}
if (sender instanceof Player && messages.size() > 0 && messages.size() > PAGE_LINE_COUNT) {
// Divide the messages into chuncks
pagedMessage.put(sender, messages);
printPage(sender, 1);
} else {
// Just print the damn thing
for (String message : messages) {
sendMessageSilently(sender, message);
}
}
}
/**
* Retrieve whitelist information about a given listener.
* @param listener - the given listener.
* @return Whitelist information.
*/
private String getWhitelistInfo(PacketListener listener) {
boolean sendingEmpty = ListeningWhitelist.isEmpty(listener.getSendingWhitelist());
boolean receivingEmpty = ListeningWhitelist.isEmpty(listener.getReceivingWhitelist());
if (!sendingEmpty && !receivingEmpty)
return String.format("Sending: %s, Receiving: %s", listener.getSendingWhitelist(), listener.getReceivingWhitelist());
else if (!sendingEmpty)
return listener.getSendingWhitelist().toString();
else if (!receivingEmpty)
return listener.getReceivingWhitelist().toString();
else
return "[None]";
}
private Set<PacketType> filterTypes(Set<PacketType> types, Sender sender) {
Set<PacketType> result = Sets.newHashSet();
for (PacketType type : types) {
if (type.getSender() == sender) {
result.add(type);
}
}
return result;
}
public PacketListener createPacketListener(Set<PacketType> type) {
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
types(filterTypes(type, Sender.SERVER)).
gamePhaseBoth().
monitor().
build();
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
types(filterTypes(type, Sender.CLIENT)).
monitor().
build();
return new PacketListener() {
@Override
public void onPacketSending(PacketEvent event) {
if (filter.filterEvent(event)) {
printInformation(event);
}
}
@Override
public void onPacketReceiving(PacketEvent event) {
if (filter.filterEvent(event)) {
printInformation(event);
}
}
private void printInformation(PacketEvent event) {
String verb = event.isServerPacket() ? "Sent" : "Received";
String format = event.isServerPacket() ?
"%s %s to %s" :
"%s %s from %s";
String shortDescription = String.format(format,
event.isCancelled() ? "Cancelled" : verb,
event.getPacketType(),
event.getPlayer().getName()
);
// Detailed will print the packet's content too
if (extendedTypes.contains(event.getPacketType())) {
try {
String original = originalPackets.remove(event);
// Also print original
if (original != null) {
logger.info("Initial packet:\n" + original + " -> ");
}
logger.info(shortDescription + ":\n" + getPacketDescription(
event.getPacket())
);
} catch (IllegalAccessException e) {
logger.log(Level.WARNING, "Unable to use reflection.", e);
}
} else {
logger.info(shortDescription + ".");
}
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return serverList;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return clientList;
}
@Override
public Plugin getPlugin() {
return plugin;
}
};
}
public PacketListener createCompareListener(Set<PacketType> type) {
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
types(filterTypes(type, Sender.SERVER)).
gamePhaseBoth().
lowest().
build();
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
types(filterTypes(type, Sender.CLIENT)).
lowest().
build();
return new PacketListener() {
@Override
public void onPacketSending(PacketEvent event) {
savePacketState(event);
}
@Override
public void onPacketReceiving(PacketEvent event) {
savePacketState(event);
}
/**
* Save the original value.
* @param event - the event with the packet to save.
*/
private void savePacketState(PacketEvent event) {
try {
originalPackets.put(event, getPacketDescription(event.getPacket()));
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot read packet.", e);
}
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return serverList;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return clientList;
}
@Override
public Plugin getPlugin() {
return plugin;
}
};
}
/**
* Retrieve a detailed string representation of the given packet.
* @param packetContainer - the packet to describe.
* @return The detailed description.
* @throws IllegalAccessException An error occured.
*/
public String getPacketDescription(PacketContainer packetContainer) throws IllegalAccessException {
Object packet = packetContainer.getHandle();
Class<?> clazz = packet.getClass();
// Get the first Minecraft super class
while (clazz != null && clazz != Object.class &&
(!MinecraftReflection.isMinecraftClass(clazz) ||
Factory.class.isAssignableFrom(clazz))) {
clazz = clazz.getSuperclass();
}
return PrettyPrinter.printObject(packet, clazz, MinecraftReflection.getPacketClass(), PrettyPrinter.RECURSE_DEPTH, new ObjectPrinter() {
@Override
public boolean print(StringBuilder output, Object value) {
// Special case
if (value instanceof byte[]) {
byte[] data = (byte[]) value;
if (data.length > HEX_DUMP_THRESHOLD) {
output.append("[");
HexDumper.defaultDumper().appendTo(output, data);
output.append("]");
return true;
}
} else if (value != null) {
EquivalentConverter<Object> converter = findConverter(value.getClass());
if (converter != null) {
output.append(converter.getSpecific(value));
return true;
}
}
return false;
}
});
}
/**
* Retrieve the closest equivalent converter to a specific class.
* @param clazz - the class.
* @return The closest converter, or NULL if not found,
*/
private EquivalentConverter<Object> findConverter(Class<?> clazz) {
Map<Class<?>, EquivalentConverter<Object>> converters = BukkitConverters.getConvertersForGeneric();
while (clazz != null) {
EquivalentConverter<Object> result = converters.get(clazz);
if (result != null)
return result;
else
clazz = clazz.getSuperclass();
}
return null;
}
public PacketListener updatePacketListener() {
if (listener != null) {
manager.removePacketListener(listener);
}
if (compareListener != null) {
manager.removePacketListener(compareListener);
}
// Register the new listeners
listener = createPacketListener(packetTypes.values());
compareListener = createCompareListener(compareTypes.values());
manager.addPacketListener(listener);
manager.addPacketListener(compareListener);
return listener;
}
private SubCommand parseCommand(Deque<String> arguments) {
String text = arguments.poll().toLowerCase();
// Parse this too
if ("add".startsWith(text))
return SubCommand.ADD;
else if ("remove".startsWith(text))
return SubCommand.REMOVE;
else if ("names".startsWith(text))
return SubCommand.NAMES;
else if ("page".startsWith(text))
return SubCommand.PAGE;
else
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.");
}
}
@@ -0,0 +1,292 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.timing.TimedListenerManager;
import com.comphenix.protocol.timing.TimingReportGenerator;
import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.Closer;
/**
* Handles the "protocol" administration command.
*
* @author Kristian
*/
class CommandProtocol extends CommandBase {
/**
* Name of this command.
*/
public static final String NAME = "protocol";
private Plugin plugin;
private Updater updater;
private ProtocolConfig config;
public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) {
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
this.plugin = plugin;
this.updater = updater;
this.config = config;
}
@Override
protected boolean handleCommand(CommandSender sender, String[] args) {
String subCommand = args[0];
// Only return TRUE if we executed the correct command
if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload")) {
reloadConfiguration(sender);
} else if (subCommand.equalsIgnoreCase("check")) {
checkVersion(sender, true);
} else if (subCommand.equalsIgnoreCase("update")) {
updateVersion(sender, true);
} else if (subCommand.equalsIgnoreCase("timings")) {
toggleTimings(sender, args);
} else if (subCommand.equalsIgnoreCase("listeners")) {
printListeners(sender);
} else if (subCommand.equalsIgnoreCase("version")) {
printVersion(sender);
} else if (subCommand.equalsIgnoreCase("dump")) {
dump(sender);
} else {
return false;
}
return true;
}
public void checkVersion(final CommandSender sender, boolean command) {
performUpdate(sender, UpdateType.NO_DOWNLOAD, command);
}
public void updateVersion(final CommandSender sender, boolean command) {
performUpdate(sender, UpdateType.DEFAULT, command);
}
// Display every listener on the server
private void printListeners(final CommandSender sender) {
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
sender.sendMessage(ChatColor.GOLD + "Packet listeners:");
for (PacketListener listener : manager.getPacketListeners()) {
sender.sendMessage(ChatColor.GOLD + " - " + listener);
}
// Along with every asynchronous listener
sender.sendMessage(ChatColor.GOLD + "Asynchronous listeners:");
for (PacketListener listener : manager.getAsynchronousManager().getAsyncHandlers()) {
sender.sendMessage(ChatColor.GOLD + " - " + listener);
}
}
private void performUpdate(final CommandSender sender, UpdateType type, final boolean command) {
if (updater.isChecking()) {
sender.sendMessage(ChatColor.RED + "Already checking for an update.");
return;
}
// Perform on an async thread
Runnable notify = new Runnable() {
@Override
public void run() {
if (command) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
String remoteVersion = updater.getRemoteVersion();
if (remoteVersion != null) {
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
}
} else if (updater.shouldNotify() || config.isDebug()) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
}
updater.removeListener(this);
updateFinished();
}
};
updater.start(type);
updater.addListener(notify);
}
private void toggleTimings(CommandSender sender, String[] args) {
TimedListenerManager manager = TimedListenerManager.getInstance();
boolean state = !manager.isTiming(); // toggle
// Parse the boolean parameter
if (args.length == 2) {
Boolean parsed = parseBoolean(toQueue(args, 2), "start");
if (parsed != null) {
state = parsed;
} else {
sender.sendMessage(ChatColor.RED + "Specify a state: ON or OFF.");
return;
}
} else if (args.length > 2) {
sender.sendMessage(ChatColor.RED + "Too many parameters.");
return;
}
// Now change the state
if (state) {
if (manager.startTiming())
sender.sendMessage(ChatColor.GOLD + "Started timing packet listeners.");
else
sender.sendMessage(ChatColor.RED + "Packet timing already started.");
} else {
if (manager.stopTiming()) {
saveTimings(manager);
sender.sendMessage(ChatColor.GOLD + "Stopped and saved result in plugin folder.");
} else {
sender.sendMessage(ChatColor.RED + "Packet timing already stopped.");
}
}
}
private void saveTimings(TimedListenerManager manager) {
try {
File destination = new File(plugin.getDataFolder(), "Timings - " + System.currentTimeMillis() + ".txt");
TimingReportGenerator generator = new TimingReportGenerator();
// Print to a text file
generator.saveTo(destination, manager);
manager.clear();
} catch (IOException e) {
reporter.reportMinimal(plugin, "saveTimings()", e);
}
}
/**
* Prevent further automatic updates until the next delay.
*/
public void updateFinished() {
long currentTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
config.setAutoLastTime(currentTime);
config.saveAll();
}
public void reloadConfiguration(CommandSender sender) {
plugin.reloadConfig();
sender.sendMessage(ChatColor.YELLOW + "Reloaded configuration!");
}
private void printVersion(CommandSender sender) {
PluginDescriptionFile desc = plugin.getDescription();
sender.sendMessage(ChatColor.GREEN + desc.getName() + ChatColor.WHITE + " v" + ChatColor.GREEN + desc.getVersion());
sender.sendMessage(ChatColor.WHITE + "Authors: " + ChatColor.GREEN + "dmulloy2" + ChatColor.WHITE + " and " + ChatColor.GREEN + "Comphenix");
sender.sendMessage(ChatColor.WHITE + "Issues: " + ChatColor.GREEN + "https://github.com/dmulloy2/ProtocolLib/issues");
}
private static SimpleDateFormat FILE_FORMAT;
private static SimpleDateFormat TIMESTAMP_FORMAT;
private void dump(CommandSender sender) {
Closer closer = Closer.create();
if (FILE_FORMAT == null)
FILE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
if (TIMESTAMP_FORMAT == null)
TIMESTAMP_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss");
try {
Date date = new Date();
File file = new File(plugin.getDataFolder(), "dump-" + FILE_FORMAT.format(date) + ".txt");
if (file.exists()) {
file.delete();
}
file.createNewFile();
FileWriter fw = closer.register(new FileWriter(file));
PrintWriter pw = closer.register(new PrintWriter(fw));
pw.println("ProtocolLib Dump");
pw.println("Timestamp: " + TIMESTAMP_FORMAT.format(date));
pw.println();
pw.println("ProtocolLib Version: " + plugin.toString());
pw.println("Bukkit Version: " + plugin.getServer().getBukkitVersion());
pw.println("Server Version: " + plugin.getServer().getVersion());
pw.println("Java Version: " + System.getProperty("java.version"));
pw.println();
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
pw.println("ProtocolLib: " + DetailedErrorReporter.getStringDescription(plugin));
pw.println("Manager: " + DetailedErrorReporter.getStringDescription(manager));
pw.println();
Set<PacketListener> listeners = manager.getPacketListeners();
Set<Plugin> plugins = new HashSet<>();
if (!listeners.isEmpty()) {
pw.println("Listeners:");
for (PacketListener listener : listeners) {
pw.println(DetailedErrorReporter.getStringDescription(listener));
Plugin plugin = listener.getPlugin();
if (plugin != null) {
plugins.add(plugin);
} else {
pw.println("(Missing plugin!)");
}
}
pw.println();
} else {
pw.println("No listeners");
}
if (!plugins.isEmpty()) {
pw.println("Plugins Using ProtocolLib:");
for (Plugin plugin : plugins) {
pw.println(plugin.getName() + " by " + plugin.getDescription().getAuthors());
}
}
sender.sendMessage("Data dump written to " + file.getAbsolutePath());
} catch (IOException ex) {
ProtocolLogger.log(Level.SEVERE, "Failed to create dump:", ex);
sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!");
} finally {
closer.close();
}
}
}
@@ -0,0 +1,163 @@
package com.comphenix.protocol;
import org.bukkit.conversations.Conversation;
import org.bukkit.conversations.ConversationCanceller;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.ExactMatchConversationCanceller;
import org.bukkit.conversations.Prompt;
import org.bukkit.conversations.StringPrompt;
/**
* Represents a conversation prompt that accepts a list of lines.
*
* @author Kristian
*/
class MultipleLinesPrompt extends StringPrompt {
/**
* Represents a canceller that determines if the multiple lines prompt is finished.
* @author Kristian
*/
public static interface MultipleConversationCanceller extends ConversationCanceller {
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine);
/**
* Determine if the current prompt is done based on the context, last
* line and collected lines.
*
* @param context - current context.
* @param currentLine - current (last) line.
* @param lines - collected lines.
* @param lineCount - number of lines.
* @return TRUE if we are done, FALSE otherwise.
*/
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
StringBuilder lines, int lineCount);
}
/**
* A wrapper class for turning a ConversationCanceller into a MultipleConversationCanceller.
* @author Kristian
*/
private static class MultipleWrapper implements MultipleConversationCanceller {
private ConversationCanceller canceller;
public MultipleWrapper(ConversationCanceller canceller) {
this.canceller = canceller;
}
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine) {
return canceller.cancelBasedOnInput(context, currentLine);
}
@Override
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
StringBuilder lines, int lineCount) {
return cancelBasedOnInput(context, currentLine);
}
@Override
public void setConversation(Conversation conversation) {
canceller.setConversation(conversation);
}
@Override
public MultipleWrapper clone() {
return new MultipleWrapper(canceller.clone());
}
}
// Feels a bit like Android
private static final String KEY = "multiple_lines_prompt";
private static final String KEY_LAST = KEY + ".last_line";
private static final String KEY_LINES = KEY + ".linecount";
private final MultipleConversationCanceller endMarker;
private final String initialPrompt;
/**
* Retrieve and remove the current accumulated input.
*
* @param context
* - conversation context.
* @return The accumulated input, or NULL if not found.
*/
public String removeAccumulatedInput(ConversationContext context) {
Object result = context.getSessionData(KEY);
if (result instanceof StringBuilder) {
context.setSessionData(KEY, null);
context.setSessionData(KEY_LINES, null);
return ((StringBuilder) result).toString();
} else {
return null;
}
}
/**
* Construct a multiple lines input prompt with a specific end marker.
* <p>
* This is usually an empty string.
*
* @param endMarker - the end marker.
*/
public MultipleLinesPrompt(String endMarker, String initialPrompt) {
this(new ExactMatchConversationCanceller(endMarker), initialPrompt);
}
/**
* Construct a multiple lines input prompt with a specific end marker implementation.
* <p>
* Note: Use {@link #MultipleLinesPrompt(MultipleConversationCanceller, String)} if implementing a custom canceller.
* @param endMarker - the end marker.
* @param initialPrompt - the initial prompt text.
*/
public MultipleLinesPrompt(ConversationCanceller endMarker, String initialPrompt) {
this.endMarker = new MultipleWrapper(endMarker);
this.initialPrompt = initialPrompt;
}
/**
* Construct a multiple lines input prompt with a specific end marker implementation.
* @param endMarker - the end marker.
* @param initialPrompt - the initial prompt text.
*/
public MultipleLinesPrompt(MultipleConversationCanceller endMarker, String initialPrompt) {
this.endMarker = endMarker;
this.initialPrompt = initialPrompt;
}
@Override
public Prompt acceptInput(ConversationContext context, String in) {
StringBuilder result = (StringBuilder) context.getSessionData(KEY);
Integer count = (Integer) context.getSessionData(KEY_LINES);
// Handle first run
if (result == null)
context.setSessionData(KEY, result = new StringBuilder());
if (count == null)
count = 0;
// Save the last line as well
context.setSessionData(KEY_LAST, in);
context.setSessionData(KEY_LINES, ++count);
result.append(in + "\n");
// And we're done
if (endMarker.cancelBasedOnInput(context, in, result, count))
return Prompt.END_OF_CONVERSATION;
else
return this;
}
@Override
public String getPromptText(ConversationContext context) {
Object last = context.getSessionData(KEY_LAST);
if (last instanceof String)
return (String) last;
else
return initialPrompt;
}
}
@@ -0,0 +1,268 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2017 Dan Mulloy
*
* 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.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.*;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.google.common.base.Charsets;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.libs.org.apache.commons.io.HexDump;
import org.bukkit.plugin.Plugin;
/**
* Logs packets to a given stream
* @author dmulloy2
*/
public class PacketLogging implements CommandExecutor, PacketListener {
public static final String NAME = "packetlog";
private List<PacketType> sendingTypes = new ArrayList<>();
private List<PacketType> receivingTypes = new ArrayList<>();
private ListeningWhitelist sendingWhitelist;
private ListeningWhitelist receivingWhitelist;
private Logger fileLogger;
private LogLocation location = LogLocation.FILE;
private final ProtocolManager manager;
private final Plugin plugin;
PacketLogging(Plugin plugin, ProtocolManager manager) {
this.plugin = plugin;
this.manager = manager;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
PacketType type = null;
try {
if (args.length > 2) {
Protocol protocol;
try {
protocol = Protocol.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
return true;
}
Sender pSender;
try {
pSender = Sender.valueOf(args[1].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
return true;
}
try {
try { // Try IDs first
int id = Integer.parseInt(args[2]);
type = PacketType.findCurrent(protocol, pSender, id);
} catch (NumberFormatException ex) { // Check packet names
String name = args[2];
for (PacketType packet : PacketType.values()) {
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
if (packet.name().equalsIgnoreCase(name)) {
type = packet;
break;
}
for (String className : packet.getClassNames()) {
if (className.equalsIgnoreCase(name)) {
type = packet;
break;
}
}
}
}
}
} catch (IllegalArgumentException ex) { // RIP
type = null;
}
if (type == null) {
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
return true;
}
if (args.length > 3) {
if (args[3].equalsIgnoreCase("console")) {
this.location = LogLocation.CONSOLE;
} else {
this.location = LogLocation.FILE;
}
}
if (pSender == Sender.CLIENT) {
if (receivingTypes.contains(type)) {
receivingTypes.remove(type);
} else {
receivingTypes.add(type);
}
} else {
if (sendingTypes.contains(type)) {
sendingTypes.remove(type);
} else {
sendingTypes.add(type);
}
}
startLogging();
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
return true;
}
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
return true;
} catch (Throwable ex) {
sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString());
return true;
}
}
private void startLogging() {
manager.removePacketListener(this);
if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
return;
}
this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();
// Setup the file logger if it hasn't been already
if (location == LogLocation.FILE && fileLogger == null) {
fileLogger = Logger.getLogger("ProtocolLib-FileLogging");
for (Handler handler : fileLogger.getHandlers())
fileLogger.removeHandler(handler);
fileLogger.setUseParentHandlers(false);
try {
File logFile = new File(plugin.getDataFolder(), "log.log");
FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
handler.setFormatter(new LogFormatter());
fileLogger.addHandler(handler);
} catch (IOException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
return;
}
}
manager.addPacketListener(this);
}
@Override
public void onPacketSending(PacketEvent event) {
log(event);
}
@Override
public void onPacketReceiving(PacketEvent event) {
log(event);
}
// Here's where the magic happens
private static String hexDump(byte[] bytes) throws IOException {
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
HexDump.dump(bytes, 0, output, 0);
return new String(output.toByteArray(), Charsets.UTF_8);
}
}
private void log(PacketEvent event) {
try {
byte[] bytes = WirePacket.bytesFromPacket(event.getPacket());
String hexDump = hexDump(bytes);
if (location == LogLocation.FILE) {
fileLogger.log(Level.INFO, event.getPacketType() + ":");
fileLogger.log(Level.INFO, hexDump);
fileLogger.log(Level.INFO, "");
} else {
System.out.println(event.getPacketType() + ":");
System.out.println(hexDump);
System.out.println();
}
} catch (Throwable ex) {
plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex);
plugin.getLogger().log(Level.WARNING, "Clearing packet logger...");
sendingTypes.clear();
receivingTypes.clear();
startLogging();
}
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
private enum LogLocation {
CONSOLE, FILE
}
private static class LogFormatter extends Formatter {
private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final String FORMAT = "[{0}] {1}";
@Override
public String format(LogRecord record) {
String string = formatMessage(record);
if (string.isEmpty()) {
return LINE_SEPARATOR;
}
StringBuilder message = new StringBuilder();
message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string));
message.append(LINE_SEPARATOR);
return message.toString();
}
}
}
@@ -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,142 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Sets;
/**
* Represents a more modern object-based enum.
* <p>
* This is useful if you want the flexibility of a modern Java enum, but don't
* want to prevent the creation of additional members dynamically.
* @author Kristian
*/
public class PacketTypeEnum implements Iterable<PacketType> {
// Used to convert between IDs and names
protected Set<PacketType> members = Sets.newHashSet();
/**
* Registers every declared PacketType field.
*/
public PacketTypeEnum() {
registerAll();
}
/**
* Registers every public assignable static field as a member.
*/
@SuppressWarnings("unchecked")
protected void registerAll() {
try {
// Register every non-deprecated field
for (Field entry : this.getClass().getFields()) {
if (Modifier.isStatic(entry.getModifiers()) && PacketType.class.isAssignableFrom(entry.getType())) {
PacketType value = (PacketType) entry.get(null);
if (value == null) {
throw new IllegalArgumentException("Field " + entry.getName() + " was null!");
}
value.setName(entry.getName());
if (entry.getAnnotation(PacketType.ForceAsync.class) != null) {
value.forceAsync();
}
boolean deprecated = entry.getAnnotation(Deprecated.class) != null;
if (deprecated) value.setDeprecated();
if (members.contains(value)) {
// Replace potentially deprecated packet types with non-deprecated ones
if (!deprecated) {
members.remove(value);
members.add(value);
}
} else {
members.add(value);
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Registers a member if its not present.
* @param instance - member instance.
* @param name - name of member.
* @return TRUE if the member was registered, FALSE otherwise.
*/
public boolean registerMember(PacketType instance, String name) {
instance.setName(name);
if (!members.contains(instance)) {
members.add(instance);
return true;
}
return false;
}
/**
* Determines whether or not the given member has been registered to this enum.
* @param member - the member to check.
* @return TRUE if the given member has been registered, FALSE otherwise.
*/
public boolean hasMember(PacketType member) {
return members.contains(member);
}
/**
* Retrieve a member by name,
* @param name - name of member to retrieve.
* @return The member, or NULL if not found.
* @deprecated Don't use this
*/
@Deprecated
public PacketType valueOf(String name) {
for (PacketType member : members) {
if (member.name().equals(name))
return member;
}
return null;
}
/**
* Retrieve every registered member.
* @return Enumeration of every value.
*/
public Set<PacketType> values() {
return new HashSet<>(members);
}
@Override
public Iterator<PacketType> iterator() {
return members.iterator();
}
}
@@ -0,0 +1,187 @@
package com.comphenix.protocol;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.collections.IntegerMap;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
/**
* Retrieve a packet type based on its version and ID, optionally with protocol and sender too.
* @author Kristian
*/
class PacketTypeLookup {
public static class ProtocolSenderLookup {
// Unroll lookup for performance reasons
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> HANDSHAKE_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> GAME_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> GAME_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> STATUS_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> STATUS_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> LOGIN_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> LOGIN_SERVER = IntegerMap.newMap();
/**
* Retrieve the correct integer map for a specific protocol and sender.
* @param protocol - the protocol.
* @param sender - the sender.
* @return The integer map of packets.
*/
public IntegerMap<PacketType> getMap(Protocol protocol, Sender sender) {
switch (protocol) {
case HANDSHAKING:
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
case PLAY:
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
case STATUS:
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
case LOGIN:
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
default:
throw new IllegalArgumentException("Unable to find protocol " + protocol);
}
}
}
public static class ClassLookup {
// Unroll lookup for performance reasons
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<String, PacketType>();
/**
* Retrieve the correct integer map for a specific protocol and sender.
* @param protocol - the protocol.
* @param sender - the sender.
* @return The integer map of packets.
*/
public Map<String, PacketType> getMap(Protocol protocol, Sender sender) {
switch (protocol) {
case HANDSHAKING:
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
case PLAY:
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
case STATUS:
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
case LOGIN:
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
default:
throw new IllegalArgumentException("Unable to find protocol " + protocol);
}
}
}
// Packet IDs from 1.6.4 and below
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> serverLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> clientLookup = new IntegerMap<PacketType>();
// Packets for 1.7.2
private final ProtocolSenderLookup idLookup = new ProtocolSenderLookup();
// Packets for 1.8+
private final ClassLookup classLookup = new ClassLookup();
// Packets based on name
private final Multimap<String, PacketType> nameLookup = HashMultimap.create();
/**
* Add a collection of packet types to the lookup.
* @param types - the types to add.
*/
public PacketTypeLookup addPacketTypes(Iterable<? extends PacketType> types) {
Preconditions.checkNotNull(types, "types cannot be NULL");
for (PacketType type : types) {
int legacy = type.getLegacyId();
// Skip unknown legacy packets
if (legacy != PacketType.UNKNOWN_PACKET) {
if (type.isServer())
serverLookup.put(type.getLegacyId(), type);
if (type.isClient())
clientLookup.put(type.getLegacyId(), type);
legacyLookup.put(type.getLegacyId(), type);
}
// Skip unknown current packets
if (type.getCurrentId() != PacketType.UNKNOWN_PACKET) {
idLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type);
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames()[0], type);
}
nameLookup.put(type.name(), type);
}
return this;
}
/**
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
* @param packetId - the legacy packet ID.
* @return The corresponding packet type, or NULL if not found.
*/
public PacketType getFromLegacy(int packetId) {
return legacyLookup.get(packetId);
}
/**
* Retrieve an unmodifiable view of all the packet types with this name.
* @param name - the name.
* @return The packet types, usually one.
*/
public Collection<PacketType> getFromName(String name) {
return Collections.unmodifiableCollection(nameLookup.get(name));
}
/**
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
* @param packetId - the legacy packet ID.
* @param preference - which packet type to look for first.
* @return The corresponding packet type, or NULL if not found.
*/
public PacketType getFromLegacy(int packetId, Sender preference) {
if (preference == Sender.CLIENT)
return getFirst(packetId, clientLookup, serverLookup);
else
return getFirst(packetId, serverLookup, clientLookup);
}
// Helper method for looking up in two sets
private <T> T getFirst(int packetId, IntegerMap<T> first, IntegerMap<T> second) {
if (first.containsKey(packetId))
return first.get(packetId);
else
return second.get(packetId);
}
/**
* Retrieve a packet type from a protocol, sender and packet ID.
* @param protocol - the current protocol.
* @param sender - the sender.
* @param packetId - the packet ID.
* @return The corresponding packet type, or NULL if not found.
* @deprecated IDs are no longer reliable
*/
@Deprecated
public PacketType getFromCurrent(Protocol protocol, Sender sender, int packetId) {
return idLookup.getMap(protocol, sender).get(packetId);
}
public PacketType getFromCurrent(Protocol protocol, Sender sender, String name) {
return classLookup.getMap(protocol, sender).get(name);
}
public ClassLookup getClassLookup() {
return classLookup;
}
}
@@ -0,0 +1,148 @@
package com.comphenix.protocol;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.ConnectionSide;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
class PacketTypeParser {
public final static Range<Integer> DEFAULT_MAX_RANGE = Range.closed(0, 255);
private Sender side = null;
private Protocol protocol = null;
public Set<PacketType> parseTypes(Deque<String> arguments, Range<Integer> defaultRange) {
Set<PacketType> result = Sets.newHashSet();
side = null;
protocol = null;
// Find these first
while (side == null) {
String arg = arguments.poll();
// Attempt to parse a side or protocol first
if (side == null) {
ConnectionSide connection = parseSide(arg);
if (connection != null) {
side = connection.getSender();
continue;
}
}
if (protocol == null) {
if ((protocol = parseProtocol(arg)) != null) {
continue;
}
}
throw new IllegalArgumentException("Specify connection side (CLIENT or SERVER).");
}
// Then we move on to parsing IDs (named packet types soon to come)
List<Range<Integer>> ranges = RangeParser.getRanges(arguments, DEFAULT_MAX_RANGE);
// And finally, parse packet names if we have a protocol
if (protocol != null) {
for (Iterator<String> it = arguments.iterator(); it.hasNext(); ) {
String name = it.next().toUpperCase(Locale.ENGLISH);
Collection<PacketType> names = PacketType.fromName(name);
for (PacketType type : names) {
if (type.getProtocol() == protocol && type.getSender() == side) {
result.add(type);
it.remove();
}
}
}
}
// Supply a default integer range
if (ranges.isEmpty() && result.isEmpty()) {
ranges = Lists.newArrayList();
ranges.add(defaultRange);
}
for (Range<Integer> range : ranges) {
for (Integer id : ContiguousSet.create(range, DiscreteDomain.integers())) {
// Deprecated packets
if (protocol == null) {
if (PacketType.hasLegacy(id)) {
result.add(PacketType.findLegacy(id, side));
}
} else {
if (PacketType.hasCurrent(protocol, side, id)) {
result.add(PacketType.findCurrent(protocol, side, id));
}
}
}
}
return result;
}
/**
* Retrieve the last parsed protocol.
* @return Last protocol.
*/
public Protocol getLastProtocol() {
return protocol;
}
/**
* Retrieve the last sender.
* @return Last sender.
*/
public Sender getLastSide() {
return side;
}
/**
* Parse a connection sides from a string.
* @param text - the possible connection side.
* @return The connection side, or NULL if not found.
*/
public ConnectionSide parseSide(String text) {
if (text == null)
return null;
String candidate = text.toLowerCase();
// Parse the side gracefully
if ("client".startsWith(candidate))
return ConnectionSide.CLIENT_SIDE;
else if ("server".startsWith(candidate))
return ConnectionSide.SERVER_SIDE;
else
return null;
}
/**
* Parse a protocol from a string.
* @param text - the possible protocol.
* @return The protocol, or NULL if not found.
*/
public Protocol parseProtocol(String text) {
if (text == null)
return null;
String candidate = text.toLowerCase();
if ("handshake".equals(candidate) || "handshaking".equals(candidate))
return Protocol.HANDSHAKING;
else if ("login".equals(candidate))
return Protocol.LOGIN;
else if ("play".equals(candidate) || "game".equals(candidate))
return Protocol.PLAY;
else if ("status".equals(candidate))
return Protocol.STATUS;
else
return null;
}
}
@@ -0,0 +1,300 @@
/*
* 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 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.
*/
private static Server INSTANCE = new Server();
public static final int KEEP_ALIVE = 0;
public static final int LOGIN = 1;
public static final int CHAT = 3;
public static final int UPDATE_TIME = 4;
public static final int ENTITY_EQUIPMENT = 5;
public static final int SPAWN_POSITION = 6;
public static final int UPDATE_HEALTH = 8;
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;
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;
public static final int MOB_SPAWN = 24;
public static final int ENTITY_PAINTING = 25;
public static final int ADD_EXP_ORB = 26;
public static final int ENTITY_VELOCITY = 28;
public static final int DESTROY_ENTITY = 29;
public static final int ENTITY = 30;
public static final int REL_ENTITY_MOVE = 31;
public static final int ENTITY_LOOK = 32;
public static final int REL_ENTITY_MOVE_LOOK = 33;
public static final int ENTITY_TELEPORT = 34;
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;
public static final int PLAY_NOTE_BLOCK = 54;
public static final int BLOCK_BREAK_ANIMATION = 55;
public static final int MAP_CHUNK_BULK = 56;
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;
public static final int CLOSE_WINDOW = 101;
public static final int SET_SLOT = 103;
public static final int WINDOW_ITEMS = 104;
public static final int CRAFT_PROGRESS_BAR = 105;
public static final int TRANSACTION = 106;
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.
*/
public static Server getRegistry() {
return INSTANCE;
}
// We only allow a single instance of this class
private Server() {
super();
}
}
/**
* 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.
*/
private static Client INSTANCE = new Client();
public static final int KEEP_ALIVE = 0;
public static final int LOGIN = 1;
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;
public static final int PLAYER_LOOK_MOVE = 13;
public static final int BLOCK_DIG = 14;
public static final int PLACE = 15;
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;
public static final int SET_CREATIVE_SLOT = 107;
public static final int BUTTON_CLICK = 108;
public static final int UPDATE_SIGN = 130;
public static final int ABILITIES = 202;
public static final int TAB_COMPLETE = 203;
public static final int LOCALE_AND_VIEW_DISTANCE = 204;
public static final int CLIENT_COMMAND = 205;
public static final int CUSTOM_PAYLOAD = 250;
public static final int KEY_RESPONSE = 252;
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.
*/
public static Client getRegistry() {
return INSTANCE;
}
// Like above
private Client() {
super();
}
}
/**
* 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);
if (serverAttempt != null)
return serverAttempt;
else
return Client.INSTANCE.valueOf(name);
}
/**
* 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);
if (serverAttempt != null)
return serverAttempt;
else
return Client.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,661 @@
/**
* 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.Set;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.executors.BukkitExecutors;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.DelegatedErrorReporter;
import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.DelayedSingleTask;
import com.comphenix.protocol.injector.InternalManager;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
/**
* The main entry point for ProtocolLib.
*
* @author Kristian
*/
public class ProtocolLib extends JavaPlugin {
// Every possible error or warning report type
public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration");
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType("Cannot delete old ProtocolLib configuration.");
public static final ReportType REPORT_CANNOT_PARSE_INJECTION_METHOD = new ReportType("Cannot parse injection method. Using default.");
public static final ReportType REPORT_PLUGIN_LOAD_ERROR = new ReportType("Cannot load ProtocolLib.");
public static final ReportType REPORT_PLUGIN_ENABLE_ERROR = new ReportType("Cannot enable ProtocolLib.");
public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType("Unable to retrieve current Minecraft version. Assuming %s");
public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType("Unable to detect conflicting plugin versions.");
public static final ReportType REPORT_CANNOT_REGISTER_COMMAND = new ReportType("Cannot register command %s: %s");
public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType("Unable to create packet timeout task.");
public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot perform automatic updates.");
// Update information
static final String BUKKIT_DEV_SLUG = "protocollib";
static final int BUKKIT_DEV_ID = 45564;
// Different commands
private enum ProtocolCommand {
FILTER,
PACKET,
PROTOCOL,
LOGGING;
}
/**
* The number of milliseconds per second.
*/
static final long MILLI_PER_SECOND = 1000;
private static final String PERMISSION_INFO = "protocol.info";
// There should only be one protocol manager, so we'll make it static
private static InternalManager protocolManager;
// Error reporter
private static ErrorReporter reporter = new BasicErrorReporter();
// Strongly typed configuration
private static ProtocolConfig config;
// Metrics and statistics
private Statistics statistics;
// Executors
private static ListeningScheduledExecutorService executorAsync;
private static ListeningScheduledExecutorService executorSync;
// Structure compiler
private BackgroundCompiler backgroundCompiler;
// Used to clean up server packets that have expired, but mostly required to simulate
// recieving client packets.
private int packetTask = -1;
private int tickCounter = 0;
private static final int ASYNC_MANAGER_DELAY = 1;
// Used to unhook players after a delay
private DelayedSingleTask unhookTask;
// Settings/options
private int configExpectedMod = -1;
// Updater
private Updater updater;
public static boolean UPDATES_DISABLED;
// Logger
private static Logger logger;
private Handler redirectHandler;
// Commands
private CommandProtocol commandProtocol;
private CommandPacket commandPacket;
private CommandFilter commandFilter;
private PacketLogging packetLogging;
// Whether or not disable is not needed
private boolean skipDisable;
@Override
public void onLoad() {
// Logging
logger = getLogger();
ProtocolLogger.init(this);
// Initialize enhancer factory
EnhancerFactory.getInstance().setClassLoader(getClassLoader());
// Initialize executors
executorAsync = BukkitExecutors.newAsynchronous(this);
executorSync = BukkitExecutors.newSynchronous(this);
// Add global parameters
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
reporter = getFilteredReporter(detailedReporter);
// Configuration
saveDefaultConfig();
reloadConfig();
try {
config = new ProtocolConfig(this);
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
// Load it again
if (deleteConfig()) {
config = new ProtocolConfig(this);
} else {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DELETE_CONFIG));
}
}
// Print the state of the debug mode
if (config.isDebug()) {
logger.warning("Debug mode is enabled!");
}
// And the state of the error reporter
if (config.isDetailedErrorReporting()) {
detailedReporter.setDetailedReporting(true);
logger.warning("Detailed error reporting enabled!");
}
try {
// Check for other versions
checkConflictingVersions();
// Handle unexpected Minecraft versions
MinecraftVersion version = verifyMinecraftVersion();
// Set updater - this will not perform any update automatically
updater = Updater.create(this, BUKKIT_DEV_ID, getFile(), UpdateType.NO_DOWNLOAD, true);
unhookTask = new DelayedSingleTask(this);
protocolManager = PacketFilterManager.newBuilder()
.classLoader(getClassLoader())
.server(getServer())
.library(this)
.minecraftVersion(version)
.unhookTask(unhookTask)
.reporter(reporter)
.build();
// Initialize the API
ProtocolLibrary.init(this, config, protocolManager, reporter, executorAsync, executorSync);
// Setup error reporter
detailedReporter.addGlobalParameter("manager", protocolManager);
// Update injection hook
try {
PlayerInjectHooks hook = config.getInjectionMethod();
// Only update the hook if it's different
if (!protocolManager.getPlayerHook().equals(hook)) {
logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook);
protocolManager.setPlayerHook(hook);
}
} catch (IllegalArgumentException e) {
reporter.reportWarning(config, Report.newBuilder(REPORT_CANNOT_PARSE_INJECTION_METHOD).error(e));
}
// Send logging information to player listeners too
initializeCommands();
setupBroadcastUsers(PERMISSION_INFO);
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
disablePlugin();
}
}
/**
* Initialize all command handlers.
*/
private void initializeCommands() {
// Initialize command handlers
for (ProtocolCommand command : ProtocolCommand.values()) {
try {
switch (command) {
case PROTOCOL:
commandProtocol = new CommandProtocol(reporter, this, updater, config);
break;
case FILTER:
commandFilter = new CommandFilter(reporter, this, config);
break;
case PACKET:
commandPacket = new CommandPacket(reporter, this, logger, commandFilter, protocolManager);
break;
case LOGGING:
packetLogging = new PacketLogging(this, protocolManager);
break;
}
} catch (OutOfMemoryError e) {
throw e;
} catch (LinkageError e) {
logger.warning("Failed to register command " + command.name() + ": " + e);
} catch (Throwable e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND)
.messageParam(command.name(), e.getMessage()).error(e));
}
}
}
/**
* Retrieve a error reporter that may be filtered by the configuration.
* @return The new default error reporter.
*/
private ErrorReporter getFilteredReporter(ErrorReporter reporter) {
return new DelegatedErrorReporter(reporter) {
private int lastModCount = -1;
private Set<String> reports = Sets.newHashSet();
@Override
protected Report filterReport(Object sender, Report report, boolean detailed) {
try {
String canonicalName = ReportType.getReportName(sender, report.getType());
String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase();
if (config != null && config.getModificationCount() != lastModCount) {
// Update our cached set again
reports = Sets.newHashSet(config.getSuppressedReports());
lastModCount = config.getModificationCount();
}
// Cancel reports either on the full canonical name, or just the report name
if (reports.contains(canonicalName) || reports.contains(reportName))
return null;
} catch (Exception e) {
// Only report this with a minor message
logger.warning("Error filtering reports: " + e.toString());
}
// Don't filter anything
return report;
}
};
}
private boolean deleteConfig() {
return config.getFile().delete();
}
@Override
public void reloadConfig() {
super.reloadConfig();
// Reload configuration
if (config != null) {
config.reloadConfig();
}
}
private void setupBroadcastUsers(final String permission) {
// Guard against multiple calls
if (redirectHandler != null)
return;
// Broadcast information to every user too
redirectHandler = new Handler() {
@Override
public void publish(LogRecord record) {
// Only display warnings and above
if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
commandPacket.broadcastMessageSilently(record.getMessage(), permission);
}
}
@Override
public void flush() {
// Not needed.
}
@Override
public void close() throws SecurityException {
// Do nothing.
}
};
logger.addHandler(redirectHandler);
}
@Override
public void onEnable() {
try {
Server server = getServer();
PluginManager manager = server.getPluginManager();
// Don't do anything else!
if (manager == null)
return;
// Silly plugin reloaders!
if (protocolManager == null) {
Logger directLogging = Logger.getLogger("Minecraft");
String[] message = new String[] {
" ProtocolLib does not support plugin reloaders! ", " Please use the built-in reload command! "
};
// Print as severe
for (String line : ChatExtensions.toFlowerBox(message, "*", 3, 1)) {
directLogging.severe(line);
}
disablePlugin();
return;
}
// Check for incompatible plugins
checkForIncompatibility(manager);
// Initialize background compiler
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);
BackgroundCompiler.setInstance(backgroundCompiler);
logger.info("Started structure compiler thread.");
} else {
logger.info("Structure compiler thread has been disabled.");
}
// Set up command handlers
registerCommand(CommandProtocol.NAME, commandProtocol);
registerCommand(CommandPacket.NAME, commandPacket);
registerCommand(CommandFilter.NAME, commandFilter);
registerCommand(PacketLogging.NAME, packetLogging);
// Player login and logout events
protocolManager.registerEvents(manager, this);
// Worker that ensures that async packets are eventually sent
// It also performs the update check.
createPacketTask(server);
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e));
disablePlugin();
return;
}
// Try to enable statistics
try {
if (config.isMetricsEnabled()) {
statistics = new Statistics(this);
}
} catch (OutOfMemoryError e) {
throw e;
} catch (IOException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(statistics));
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam(statistics));
}
}
// Plugin authors: Notify me to remove these
private void checkForIncompatibility(PluginManager manager) {
for (String plugin : ProtocolLibrary.INCOMPATIBLE) {
if (manager.getPlugin(plugin) != null) {
// Special case for TagAPI and iTag
if (plugin.equals("TagAPI")) {
Plugin iTag = manager.getPlugin("iTag");
if (iTag == null || iTag.getDescription().getVersion().startsWith("1.0")) {
logger.severe("Detected incompatible plugin: TagAPI");
}
} else {
logger.severe("Detected incompatible plugin: " + plugin);
}
}
}
}
// Used to check Minecraft version
private MinecraftVersion verifyMinecraftVersion() {
MinecraftVersion minimum = new MinecraftVersion(ProtocolLibrary.MINIMUM_MINECRAFT_VERSION);
MinecraftVersion maximum = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION);
try {
MinecraftVersion current = new MinecraftVersion(getServer());
// Skip certain versions
if (!config.getIgnoreVersionCheck().equals(current.getVersion())) {
// We'll just warn the user for now
if (current.compareTo(minimum) < 0)
logger.warning("Version " + current + " is lower than the minimum " + minimum);
if (current.compareTo(maximum) > 0)
logger.warning("Version " + current + " has not yet been tested! Proceed with caution.");
}
return current;
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e).messageParam(maximum));
// Unknown version - just assume it is the latest
return maximum;
}
}
private void checkConflictingVersions() {
Pattern ourPlugin = Pattern.compile("ProtocolLib-(.*)\\.jar");
MinecraftVersion currentVersion = new MinecraftVersion(getDescription().getVersion());
MinecraftVersion newestVersion = null;
// Skip the file that contains this current instance however
File loadedFile = getFile();
try {
// Scan the plugin folder for newer versions of ProtocolLib
// The plugin folder isn't always plugins/
File pluginFolder = getDataFolder().getParentFile();
File[] candidates = pluginFolder.listFiles();
if (candidates != null) {
for (File candidate : candidates) {
if (candidate.isFile() && !candidate.equals(loadedFile)) {
Matcher match = ourPlugin.matcher(candidate.getName());
if (match.matches()) {
MinecraftVersion version = new MinecraftVersion(match.group(1));
if (candidate.length() == 0) {
// Delete and inform the user
logger.info((candidate.delete() ? "Deleted " : "Could not delete ") + candidate);
} else if (newestVersion == null || newestVersion.compareTo(version) < 0) {
newestVersion = version;
}
}
}
}
}
} catch (Exception e) {
// TODO This shows [ProtocolLib] and [ProtocolLibrary] in the message
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS).error(e));
}
// See if the newest version is actually higher
if (newestVersion != null && currentVersion.compareTo(newestVersion) < 0) {
// We don't need to set internal classes or instances to NULL - that would break the other loaded plugin
skipDisable = true;
throw new IllegalStateException(String.format(
"Detected a newer version of ProtocolLib (%s) in plugin folder than the current (%s). Disabling.",
newestVersion.getVersion(), currentVersion.getVersion()));
}
}
private void registerCommand(String name, CommandExecutor executor) {
try {
// Ignore these - they must have printed an error already
if (executor == null)
return;
PluginCommand command = getCommand(name);
// Try to load the command
if (command != null) {
command.setExecutor(executor);
} else {
throw new RuntimeException("plugin.yml might be corrupt.");
}
} catch (RuntimeException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
}
}
/**
* Disable the current plugin.
*/
private void disablePlugin() {
getServer().getPluginManager().disablePlugin(this);
}
private void createPacketTask(Server server) {
try {
if (packetTask >= 0)
throw new IllegalStateException("Packet task has already been created");
// Attempt to create task
packetTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
@Override
public void run() {
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
// We KNOW we're on the main thread at the moment
manager.sendProcessedPackets(tickCounter++, true);
// House keeping
updateConfiguration();
// Check for updates too
if (!UPDATES_DISABLED && (tickCounter % 20) == 0) {
checkUpdates();
}
}
}, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY);
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
if (packetTask == -1) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CREATE_TIMEOUT_TASK).error(e));
}
}
}
private void updateConfiguration() {
if (config != null && config.getModificationCount() != configExpectedMod) {
configExpectedMod = config.getModificationCount();
// Update the debug flag
protocolManager.setDebug(config.isDebug());
}
}
private void checkUpdates() {
// Ignore milliseconds - it's pointless
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
try {
long updateTime = config.getAutoLastTime() + config.getAutoDelay();
// Should we update?
if (currentTime > updateTime && !updater.isChecking()) {
// Initiate the update as if it came from the console
if (config.isAutoDownload())
commandProtocol.updateVersion(getServer().getConsoleSender(), false);
else if (config.isAutoNotify())
commandProtocol.checkVersion(getServer().getConsoleSender(), false);
else
commandProtocol.updateFinished();
}
} catch (Exception e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
UPDATES_DISABLED = true;
}
}
@Override
public void onDisable() {
if (skipDisable) {
return;
}
// Disable compiler
if (backgroundCompiler != null) {
backgroundCompiler.shutdownAll();
backgroundCompiler = null;
BackgroundCompiler.setInstance(null);
}
// Clean up
if (packetTask >= 0) {
getServer().getScheduler().cancelTask(packetTask);
packetTask = -1;
}
// And redirect handler too
if (redirectHandler != null) {
logger.removeHandler(redirectHandler);
}
if (protocolManager != null)
protocolManager.close();
else
return; // Plugin reloaders!
if (unhookTask != null)
unhookTask.close();
protocolManager = null;
statistics = null;
// To clean up global parameters
reporter = new BasicErrorReporter();
}
/**
* 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. It is also
* NULL if metrics has been disabled.
* @return Metrics instance container.
*/
public Statistics getStatistics() {
return statistics;
}
public ProtocolConfig getProtocolConfig() {
return config;
}
}
@@ -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.15.1";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.14.4) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2019-12-17";
/**
* 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,80 @@
/**
* 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 java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
/**
* @author dmulloy2
*/
public class ProtocolLogger {
private static boolean debugEnabled = false;
private static Logger logger = Logger.getLogger("Minecraft");
protected static void init(Plugin plugin) {
logger = plugin.getLogger();
try {
debugEnabled = plugin.getConfig().getBoolean("global.debug", false);
} catch (Throwable ignored) { }
}
/**
* 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) {
logger.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) {
logger.log(level, message, ex);
}
public static void debug(String message, Object... args) {
if (debugEnabled) {
log("[Debug] " + message, args);
}
}
public static void debug(String message, Throwable ex) {
if (debugEnabled) {
logger.log(Level.WARNING, "[Debug] " + message, ex);
}
}
}
@@ -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,164 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Range;
/**
* Used to parse ranges in CommandPacket.
*
* @author Kristian
*/
class RangeParser {
/**
* Parse a range from a given text.
* @param text - the text.
* @param legalRange - range of legal values.
* @return The parsed ranges.
*/
public static List<Range<Integer>> getRanges(String text, Range<Integer> legalRange) {
return getRanges(new ArrayDeque<String>(Arrays.asList(text)), legalRange);
}
/**
* Parse ranges from an array of elements.
* @param args - array of elements.
* @param offset - beginning offset.
* @param lastIndex - the last index of the array to read.
* @param legalRange - range of legal values.
* @return The parsed ranges.
*/
public static List<Range<Integer>> getRanges(Deque<String> input, Range<Integer> legalRange) {
List<String> tokens = tokenizeInput(input);
List<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
for (int i = 0; i < tokens.size(); i++) {
Range<Integer> range;
String current = tokens.get(i);
String next = i + 1 < tokens.size() ? tokens.get(i + 1) : null;
// Yoda equality is done for null-safety
if ("-".equals(current)) {
throw new IllegalArgumentException("A hyphen must appear between two numbers.");
} else if ("-".equals(next)) {
if (i + 2 >= tokens.size())
throw new IllegalArgumentException("Cannot form a range without a upper limit.");
// This is a proper range
range = Range.closed(Integer.parseInt(current), Integer.parseInt(tokens.get(i + 2)));
ranges.add(range);
// Skip the two next tokens
i += 2;
} else {
// Just a single number
range = Range.singleton(Integer.parseInt(current));
ranges.add(range);
}
// Validate ranges
if (!legalRange.encloses(range)) {
throw new IllegalArgumentException(range + " is not in the range " + range.toString());
}
}
return simplify(ranges, legalRange.upperEndpoint());
}
/**
* Simplify a list of ranges by assuming a maximum value.
* @param ranges - the list of ranges to simplify.
* @param maximum - the maximum value (minimum value is always 0).
* @return A simplified list of ranges.
*/
private static List<Range<Integer>> simplify(List<Range<Integer>> ranges, int maximum) {
List<Range<Integer>> result = new ArrayList<Range<Integer>>();
boolean[] set = new boolean[maximum + 1];
int start = -1;
// Set every ID
for (Range<Integer> range : ranges) {
for (int id : ContiguousSet.create(range, DiscreteDomain.integers())) {
set[id] = true;
}
}
// Generate ranges from this set
for (int i = 0; i <= set.length; i++) {
if (i < set.length && set[i]) {
if (start < 0) {
start = i;
}
} else {
if (start >= 0) {
result.add(Range.closed(start, i - 1));
start = -1;
}
}
}
return result;
}
private static List<String> tokenizeInput(Deque<String> input) {
List<String> tokens = new ArrayList<String>();
// Tokenize the input
while (!input.isEmpty()) {
StringBuilder number = new StringBuilder();
String text = input.peek();
for (int j = 0; j < text.length(); j++) {
char current = text.charAt(j);
if (Character.isDigit(current)) {
number.append(current);
} else if (Character.isWhitespace(current)) {
// That's ok
} else if (current == '-') {
// Add the number token first
if (number.length() > 0) {
tokens.add(number.toString());
number.setLength(0);
}
tokens.add(Character.toString(current));
} else {
// We're no longer dealing with integers - quit
return tokens;
}
}
// Add the number token, if it hasn't already
if (number.length() > 0)
tokens.add(number.toString());
input.poll();
}
return tokens;
}
}
@@ -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 = syncPacket.getPlayer();
if (player != null) {
// Start the process
getSendingQueue(syncPacket).enqueue(newEvent);
// We know this is occurring 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);
}
}
@@ -0,0 +1,704 @@
/*
* 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.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 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.google.common.base.Function;
import com.google.common.base.Joiner;
import org.bukkit.plugin.Plugin;
/**
* 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.
*/
private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object());
/**
* Called when the threads have to wake up for something important.
*/
private static final PacketEvent WAKEUP_PACKET = new PacketEvent(new Object());
/**
* The expected number of ticks per second.
*/
private static final int TICKS_PER_SECOND = 20;
// Unique worker ID
private static final AtomicInteger nextID = new AtomicInteger();
// Default queue capacity
private static final int DEFAULT_CAPACITY = 1024;
// Cancel the async handler
private volatile boolean cancelled;
// Number of worker threads
private final AtomicInteger started = new AtomicInteger();
// The packet listener
private PacketListener listener;
// The filter manager
private AsyncFilterManager filterManager;
private NullPacketListener nullPacketListener;
// List of queued packets
private ArrayBlockingQueue<PacketEvent> queuedPackets = new ArrayBlockingQueue<PacketEvent>(DEFAULT_CAPACITY);
// List of cancelled tasks
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;
// 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)
throw new IllegalArgumentException("listener cannot be NULL");
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;
}
/**
* Set the synchronized listener that has been automatically created.
* @param nullPacketListener - automatically created listener.
*/
void setNullPacketListener(NullPacketListener nullPacketListener) {
this.nullPacketListener = nullPacketListener;
}
/**
* Retrieve the synchronized listener that was automatically created.
* @return Automatically created listener.
*/
PacketListener getNullPacketListener() {
return nullPacketListener;
}
/**
* Retrieve the plugin associated with this async listener.
* @return The plugin.
*/
public Plugin getPlugin() {
return listener != null ? listener.getPlugin() : null;
}
/**
* Cancel the handler.
*/
public void cancel() {
// Remove the listener as quickly as possible
close();
}
/**
* Queue a packet for processing.
* @param packet - a packet for processing.
* @throws IllegalStateException If the underlying packet queue is full.
*/
public void enqueuePacket(PacketEvent packet) {
if (packet == null)
throw new IllegalArgumentException("packet is NULL");
queuedPackets.add(packet);
}
/**
* Create a worker that will initiate the listener loop. Note that using stop() to
* 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() {
private final AtomicBoolean firstRun = new AtomicBoolean();
private final AtomicBoolean finished = new AtomicBoolean();
private final int id = nextID.incrementAndGet();
@Override
public int getID() {
return id;
}
@Override
public void run() {
// Careful now
if (firstRun.compareAndSet(false, true)) {
listenerLoop(id);
synchronized (stopLock) {
stoppedTasks.remove(id);
stopLock.notifyAll();
finished.set(true);
}
} else {
if (finished.get())
throw new IllegalStateException(
"This listener has already been run. Create a new instead.");
else
throw new IllegalStateException(
"This listener loop has already been started. Create a new instead.");
}
}
@Override
public boolean stop() throws InterruptedException {
synchronized (stopLock) {
if (!isRunning())
return false;
stoppedTasks.add(id);
// Wake up threads - we have a listener to stop
for (int i = 0; i < getWorkers(); i++) {
queuedPackets.offer(WAKEUP_PACKET);
}
finished.set(true);
waitForStops();
return true;
}
}
@Override
public boolean isRunning() {
return firstRun.get() && !finished.get();
}
@Override
public boolean isFinished() {
return finished.get();
}
};
}
/**
* Start a singler worker thread handling the asynchronous listener.
*/
public synchronized void start() {
if (listener.getPlugin() == null)
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
if (cancelled)
throw new IllegalStateException("Cannot start a worker when the listener is closing.");
final AsyncRunnable listenerLoop = getListenerLoop();
stopWarningTask();
scheduleAsync(new Runnable() {
@Override
public void run() {
Thread thread = Thread.currentThread();
String previousName = thread.getName();
String workerName = getFriendlyWorkerName(listenerLoop.getID());
// Add the friendly worker name
thread.setName(workerName);
listenerLoop.run();
thread.setName(previousName);
}
});
}
/**
* Start a singler worker thread handling the asynchronous listener.
* <p>
* This method is intended to allow callers to customize the thread priority
* before the worker loop is actually called. This is simpler than to
* schedule the worker threads manually.
* <pre><code>
* listenerHandler.start(new Function&lt;AsyncRunnable, Void&gt;() {
* &#64;Override
* 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);
* return null;
* }
* });
* }
* </code></pre>
* @param executor - a method that will execute the given listener loop.
*/
public synchronized void start(Function<AsyncRunnable, Void> executor) {
if (listener.getPlugin() == null)
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
if (cancelled)
throw new IllegalStateException("Cannot start a worker when the listener is closing.");
final AsyncRunnable listenerLoop = getListenerLoop();
final Function<AsyncRunnable, Void> delegateCopy = executor;
scheduleAsync(new Runnable() {
@Override
public void run() {
delegateCopy.apply(listenerLoop);
}
});
}
private void scheduleAsync(Runnable runnable) {
listener.getPlugin().getServer().getScheduler().runTaskAsynchronously(listener.getPlugin(), runnable);
}
/**
* Create a friendly thread name using the following convention:
* <p><code>
* &nbsp;&nbsp;&nbsp;&nbsp;Protocol Worker {id} - {plugin} - [recv: {packets}, send: {packets}]
* </code></p>
* @param id - the worker ID.
* @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),
fromWhitelist(listener.getReceivingWhitelist()),
fromWhitelist(listener.getSendingWhitelist())
);
}
/**
* Convert the given whitelist to a comma-separated list of packet IDs.
* @param whitelist - the whitelist.
* @return A comma separated list of packet IDs in the whitelist, or the emtpy string.
*/
private String fromWhitelist(ListeningWhitelist whitelist) {
if (whitelist == null)
return "";
else
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;
}
}
/**
* Start multiple worker threads for this listener.
* @param count - number of worker threads to start.
*/
public synchronized void start(int count) {
for (int i = 0; i < count; i++)
start();
}
/**
* Stop a worker thread.
*/
public synchronized void stop() {
queuedPackets.add(INTERUPT_PACKET);
}
/**
* Stop the given amount of worker threads.
* @param count - number of threads to stop.
*/
public synchronized void stop(int count) {
for (int i = 0; i < count; i++)
stop();
}
/**
* 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.
*/
public synchronized void setWorkers(int count) {
if (count < 0)
throw new IllegalArgumentException("Number of workers cannot be less than zero.");
if (count > DEFAULT_CAPACITY)
throw new IllegalArgumentException("Cannot initiate more than " + DEFAULT_CAPACITY + " workers");
if (cancelled && count > 0)
throw new IllegalArgumentException("Cannot add workers when the listener is closing.");
long time = System.currentTimeMillis();
// Try to get to the correct count
while (started.get() != count) {
if (started.get() < count)
start();
else
stop();
// May happen if another thread is doing something similar to "setWorkers"
if ((System.currentTimeMillis() - time) > 50)
throw new RuntimeException("Failed to set worker count.");
}
}
/**
* Retrieve the current number of registered workers.
* <p>
* Note that the returned value may be out of data.
* @return Number of registered workers.
*/
public synchronized int getWorkers() {
return started.get();
}
/**
* Wait until every tasks scheduled to stop has actually stopped.
* @return TRUE if the current listener should stop, FALSE otherwise.
* @throws InterruptedException - If the current thread was interrupted.
*/
private boolean waitForStops() throws InterruptedException {
synchronized (stopLock) {
while (stoppedTasks.size() > 0 && !cancelled) {
stopLock.wait();
}
return cancelled;
}
}
/**
* 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())
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.");
try {
// Wait if certain threads are stopping
if (waitForStops())
return;
// Proceed
started.incrementAndGet();
while (!cancelled) {
PacketEvent packet = queuedPackets.take();
// Handle cancel requests
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))
return;
if (waitForStops())
return;
}
} else if (packet == INTERUPT_PACKET) {
return;
}
if (packet != null && packet.getAsyncMarker() != null) {
processPacket(workerID, packet, "onAsyncPacket()");
}
}
} catch (InterruptedException e) {
// We're done
} finally {
// Clean up
started.decrementAndGet();
}
}
/**
* 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();
}
}
/**
* Use the poision pill method to stop every worker thread.
*/
private void stopThreads() {
// Poison Pill Shutdown
queuedPackets.clear();
stop(started.get());
// Individual shut down is irrelevant now
synchronized (stopLock) {
stopLock.notifyAll();
}
}
}
@@ -0,0 +1,487 @@
/*
* 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.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
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;
/**
* Contains information about the packet that is being processed by asynchronous listeners.
* <p>
* Asynchronous listeners can use this to set packet timeout or transmission order.
*
* @author Kristian
*/
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = -2621498096616187384L;
/**
* Default number of milliseconds until a packet will rejected.
*/
public static final int DEFAULT_TIMEOUT_DELTA = 1800 * 1000;
/**
* Default number of packets to skip.
*/
public static final int DEFAULT_SENDING_DELTA = 0;
/**
* The packet stream responsible for transmitting the packet when it's done processing.
*/
private transient PacketStream packetStream;
/**
* Current list of async packet listeners.
*/
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
// Timeout handling
private long initialTime;
private long timeout;
// Packet order
private long originalSendingIndex;
private long newSendingIndex;
// Used to determine if a packet must be reordered in the sending queue
private Long queuedSendingIndex;
// Whether or not the packet has been processed by the listeners
private volatile boolean processed;
// Whether or not the packet has been sent
private volatile boolean transmitted;
// Whether or not the asynchronous processing itself should be cancelled
private volatile boolean asyncCancelled;
// Whether or not to delay processing
private AtomicInteger processingDelay = new AtomicInteger();
// Used to synchronize processing on the shared PacketEvent
private Object processingLock = new Object();
// Used to identify the asynchronous worker
private transient AsyncListenerHandler listenerHandler;
private transient int workerID;
// Determine if Minecraft processes this packet asynchronously
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 initialTime, long timeoutDelta) {
if (packetStream == null)
throw new IllegalArgumentException("packetStream cannot be NULL");
this.packetStream = packetStream;
// Timeout
this.initialTime = initialTime;
this.timeout = initialTime + timeoutDelta;
// Sending index
this.originalSendingIndex = sendingIndex;
this.newSendingIndex = sendingIndex;
}
/**
* 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.
*/
public long getInitialTime() {
return initialTime;
}
/**
* Retrieve the time the packet will be forcefully rejected.
* @return The time to reject the packet, in milliseconds since 01.01.1970 00:00.
*/
public long getTimeout() {
return timeout;
}
/**
* Set the time the packet will be forcefully rejected.
* @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00.
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* Retrieve the order the packet was originally transmitted.
* @return The original packet index.
*/
public long getOriginalSendingIndex() {
return originalSendingIndex;
}
/**
* Retrieve the desired sending order after processing has completed.
* <p>
* Higher sending order means lower priority.
* @return Desired sending order.
*/
public long getNewSendingIndex() {
return newSendingIndex;
}
/**
* Sets the desired sending order after processing has completed.
* <p>
* Higher sending order means lower priority.
* @param newSendingIndex - new packet send index.
*/
public void setNewSendingIndex(long newSendingIndex) {
this.newSendingIndex = newSendingIndex;
}
/**
* Retrieve the packet stream responsible for transmitting this packet.
* @return The packet stream.
*/
public PacketStream getPacketStream() {
return packetStream;
}
/**
* Sets the output packet stream responsible for transmitting this packet.
* @param packetStream - new output packet stream.
*/
public void setPacketStream(PacketStream packetStream) {
this.packetStream = packetStream;
}
/**
* Retrieve whether or not this packet has been processed by the async listeners.
* @return TRUE if it has been processed, FALSE otherwise.
*/
public boolean isProcessed() {
return processed;
}
/**
* Sets whether or not this packet has been processed by the async listeners.
* @param processed - TRUE if it has, FALSE otherwise.
*/
void setProcessed(boolean processed) {
this.processed = processed;
}
/**
* 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
* {@link AsyncFilterManager#signalPacketTransmission(PacketEvent)},
* 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
* using the {@link #getProcessingLock()} method.
*
* @return The new processing delay.
*/
public int incrementProcessingDelay() {
return processingDelay.incrementAndGet();
}
/**
* Decrement the number of times this packet must be signalled as done before it's transmitted.
* @return The new processing delay. If zero, the packet should be sent.
*/
int decrementProcessingDelay() {
return processingDelay.decrementAndGet();
}
/**
* Retrieve the number of times a packet must be signalled to be done before it's sent.
* @return Number of processing delays.
*/
public int getProcessingDelay() {
return processingDelay.get();
}
/**
* Whether or not this packet is or has been queued for processing.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isQueued() {
return queuedSendingIndex != null;
}
/**
* Retrieve the sending index when the packet was queued.
* @return Queued sending index.
*/
public long getQueuedSendingIndex() {
return queuedSendingIndex != null ? queuedSendingIndex : 0;
}
/**
* Set the sending index when the packet was queued.
* @param queuedSendingIndex - sending index.
*/
void setQueuedSendingIndex(Long queuedSendingIndex) {
this.queuedSendingIndex = queuedSendingIndex;
}
/**
* Processing lock used to synchronize access to the parent PacketEvent and PacketContainer.
* <p>
* This lock is automatically acquired for every asynchronous packet listener. It should only be
* used to synchronize access to a PacketEvent if it's processing has been delayed.
* @return A processing lock.
*/
public Object getProcessingLock() {
return processingLock;
}
public void setProcessingLock(Object processingLock) {
this.processingLock = processingLock;
}
/**
* Retrieve whether or not this packet has already been sent.
* @return TRUE if it has been sent before, FALSE otherwise.
*/
public boolean isTransmitted() {
return transmitted;
}
/**
* Determine if this packet has expired.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean hasExpired() {
return hasExpired(System.currentTimeMillis());
}
/**
* Determine if this packet has expired given this time.
* @param currentTime - the current time in milliseconds since 01.01.1970 00:00.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean hasExpired(long currentTime) {
return timeout < currentTime;
}
/**
* Determine if the asynchronous handling should be cancelled.
* @return TRUE if it should, FALSE otherwise.
*/
public boolean isAsyncCancelled() {
return asyncCancelled;
}
/**
* Set whether or not the asynchronous handling should be cancelled.
* <p>
* This is only relevant during the synchronous processing. Asynchronous
* listeners should use the normal cancel-field to cancel a PacketEvent.
*
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
*/
public void setAsyncCancelled(boolean asyncCancelled) {
this.asyncCancelled = asyncCancelled;
}
/**
* Retrieve the current asynchronous listener handler.
* @return Asychronous listener handler, or NULL if this packet is not asynchronous.
*/
public AsyncListenerHandler getListenerHandler() {
return listenerHandler;
}
/**
* Set the current asynchronous listener handler.
* <p>
* Used by the worker to update the value.
* @param listenerHandler - new listener handler.
*/
void setListenerHandler(AsyncListenerHandler listenerHandler) {
this.listenerHandler = listenerHandler;
}
/**
* Retrieve the current worker ID.
* @return Current worker ID.
*/
public int getWorkerID() {
return workerID;
}
/**
* Set the current worker ID.
* <p>
* Used by the worker.
* @param workerID - new worker ID.
*/
void setWorkerID(int workerID) {
this.workerID = workerID;
}
/**
* Retrieve iterator for the next listener in line.
* @return Next async packet listener iterator.
*/
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
return listenerTraversal;
}
/**
* Set the iterator for the next listener.
* @param listenerTraversal - the new async packet listener iterator.
*/
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
this.listenerTraversal = listenerTraversal;
}
/**
* Transmit a given packet to the current packet stream.
* @param event - the packet to send.
* @throws IOException If the packet couldn't be sent.
*/
void sendPacket(PacketEvent event) throws IOException {
try {
if (event.isServerPacket()) {
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
} else {
packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
}
transmitted = true;
} catch (InvocationTargetException e) {
throw new IOException("Cannot send packet", e);
} catch (IllegalAccessException e) {
throw new IOException("Cannot send packet", e);
}
}
/**
* 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(MinecraftReflection.getPacketClass()).getMethodByName("a_.*");
} catch (RuntimeException e) {
// This will occur in 1.2.5 (or possibly in later versions)
List<Method> methods = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
getMethodListByParameters(boolean.class, new Class[] {});
// Try to look for boolean methods
if (methods.size() == 2) {
isMinecraftAsync = methods.get(1);
} 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 {
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
return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle());
} catch (IllegalArgumentException e) {
throw new FieldAccessException("Illegal argument", e);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Unable to reflect method call 'a_', or: isAsyncPacket.", e);
} catch (InvocationTargetException e) {
throw new FieldAccessException("Minecraft error", e);
}
}
}
@Override
public int compareTo(AsyncMarker o) {
if (o == null)
return 1;
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());
}
}
@@ -0,0 +1,53 @@
/*
* 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;
/**
* A runnable representing a asynchronous event listener.
*
* @author Kristian
*/
public interface AsyncRunnable extends Runnable {
/**
* Retrieve a unique worker ID.
* @return Unique worker ID.
*/
public int getID();
/**
* Stop the given 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;
/**
* Determine if we're running or not.
* @return TRUE if we're running, FALSE otherwise.
*/
public boolean isRunning();
/**
* Determine if this runnable has already run its course.
* @return TRUE if it has been stopped, FALSE otherwise.
*/
boolean isFinished();
}
@@ -0,0 +1,81 @@
/*
* 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 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;
import com.comphenix.protocol.events.PacketListener;
/**
* Represents a NO OPERATION listener.
*
* @author Kristian
*/
class NullPacketListener implements PacketListener {
private ListeningWhitelist sendingWhitelist;
private ListeningWhitelist receivingWhitelist;
private Plugin plugin;
/**
* Create a no-op listener with the same whitelist and plugin as the given listener.
* @param original - the packet listener to copy.
*/
public NullPacketListener(PacketListener original) {
this.sendingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getSendingWhitelist());
this.receivingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getReceivingWhitelist());
this.plugin = original.getPlugin();
}
@Override
public void onPacketSending(PacketEvent event) {
// NULL
}
@Override
public void onPacketReceiving(PacketEvent event) {
// NULL
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) {
if (whitelist != null)
// 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;
}
@Override
public Plugin getPlugin() {
return plugin;
}
}
@@ -0,0 +1,76 @@
/*
* 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 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.
*
* @author Kristian
*/
class PacketEventHolder implements Comparable<PacketEventHolder> {
private PacketEvent event;
private long sendingIndex = 0;
/**
* A wrapper that ensures the packet event is ordered by sending index.
* @param event - packet event to wrap.
*/
public PacketEventHolder(PacketEvent event) {
this.event = Preconditions.checkNotNull(event, "Event must be non-null");
if (event.getAsyncMarker() != null)
this.sendingIndex = event.getAsyncMarker().getNewSendingIndex();
}
/**
* Retrieve the stored event.
* @return The stored event.
*/
public PacketEvent getEvent() {
return event;
}
@Override
public int compareTo(PacketEventHolder other) {
return ComparisonChain.start().
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);
}
}
@@ -0,0 +1,196 @@
/*
* 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.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;
/**
* Handles the processing of every packet type.
*
* @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;
/**
* Default maximum number of packets to process concurrently.
*/
public static final int DEFAULT_MAXIMUM_CONCURRENCY = 32;
/**
* Default maximum number of packets to queue for processing.
*/
public static final int DEFAULT_QUEUE_LIMIT = 1024 * 60;
/**
* Number of packets we're processing concurrently.
*/
private final int maximumConcurrency;
private Semaphore concurrentProcessing;
// Queued packets for being processed
private Queue<PacketEventHolder> processingQueue;
// Packets for sending
private PlayerSendingHandler sendingHandler;
public PacketProcessingQueue(PlayerSendingHandler sendingHandler) {
this(sendingHandler, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
}
public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize, int maximumConcurrency) {
super();
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.sendingHandler = sendingHandler;
}
/**
* Enqueue a packet for processing by the asynchronous listeners.
* @param packet - packet to process.
* @param onMainThread - whether or not this is occuring on the main thread.
* @return TRUE if we sucessfully queued the packet, FALSE if the queue ran out if space.
*/
public boolean enqueue(PacketEvent packet, boolean onMainThread) {
try {
processingQueue.add(new PacketEventHolder(packet));
// Begin processing packets
signalBeginProcessing(onMainThread);
return true;
} catch (IllegalStateException e) {
return false;
}
}
/**
* Number of packet events in the queue.
* @return The number of packet events in the queue.
*/
public int size() {
return processingQueue.size();
}
/**
* Called by the current method and each thread to signal that a packet might be ready for processing.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public void signalBeginProcessing(boolean onMainThread) {
while (concurrentProcessing.tryAcquire()) {
PacketEventHolder holder = processingQueue.poll();
// Any packet queued?
if (holder != null) {
PacketEvent packet = holder.getEvent();
AsyncMarker marker = packet.getAsyncMarker();
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketType());
marker.incrementProcessingDelay();
// Yes, removing the marker will cause the chain to stop
if (list != null) {
Iterator<PrioritizedListener<AsyncListenerHandler>> iterator = list.iterator();
if (iterator.hasNext()) {
marker.setListenerTraversal(iterator);
iterator.next().getListener().enqueuePacket(packet);
continue;
}
}
// The packet has no further listeners. Just send it.
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 {
// No more queued packets.
signalProcessingDone();
return;
}
}
}
/**
* Called when a packet has been processed.
*/
public void signalProcessingDone() {
concurrentProcessing.release();
}
/**
* Retrieve the maximum number of packets to process at any given time.
* @return Number of simultaneous packet to process.
*/
public int getMaximumConcurrency() {
return maximumConcurrency;
}
public void cleanupAll() {
// Cancel all the threads and every listener
for (PrioritizedListener<AsyncListenerHandler> handler : values()) {
if (handler != null) {
handler.getListener().cancel();
}
}
// Remove the rest, just in case
clearListeners();
// Remove every packet in the queue
processingQueue.clear();
}
}
@@ -0,0 +1,307 @@
/*
* 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.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;
/**
* Represents packets ready to be transmitted to a client.
* @author Kristian
*/
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;
// 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.
* @return The number of packet events in the queue.
*/
public int size() {
return sendingQueue.size();
}
/**
* Enqueue a packet for sending.
* @param packet - packet to queue.
*/
public void enqueue(PacketEvent packet) {
sendingQueue.add(new PacketEventHolder(packet));
}
/**
* Invoked when one of the packets have finished processing.
* @param packetUpdated - the packet that has now been updated.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
AsyncMarker marker = packetUpdated.getAsyncMarker();
// Should we reorder the event?
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
enqueue(copy);
}
// Mark this packet as finished
marker.setProcessed(true);
trySendPackets(onMainThread);
}
/***
* Invoked when a list of packet IDs are no longer associated with any listeners.
* @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<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.getPacketType())) {
event.getAsyncMarker().setProcessed(true);
}
}
// This is likely to have changed the situation a bit
trySendPackets(onMainThread);
}
/**
* Attempt to send any remaining packets.
* @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 (sending) {
PacketEventHolder holder = sendingQueue.poll();
if (holder != null) {
sending = processPacketHolder(onMainThread, holder);
if (!sending) {
// Add it back again
sendingQueue.add(holder);
}
} 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;
// 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();
// Just drop the packet
return true;
}
}
// Silently skip players that have logged out
if (isOnline(current.getPlayer())) {
sendPacket(current);
}
}
// 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();
}
/**
* Send every packet, regardless of the processing state.
*/
private void forceSend() {
while (true) {
PacketEventHolder holder = sendingQueue.poll();
if (holder != null) {
sendPacket(holder.getEvent());
} else {
break;
}
}
}
/**
* Whether or not the packet transmission must synchronize with the main thread.
* @return TRUE if it must, FALSE otherwise.
*/
public boolean isSynchronizeMain() {
return notThreadSafe;
}
/**
* Transmit a packet, if it hasn't already.
* @param event - the packet to transmit.
*/
private void sendPacket(PacketEvent event) {
AsyncMarker marker = event.getAsyncMarker();
try {
// Don't send a packet twice
if (marker != null && !marker.isTransmitted()) {
marker.sendPacket(event);
}
} catch (PlayerLoggedOutException e) {
ProtocolLibrary.getErrorReporter().reportDebug(this, Report.newBuilder(REPORT_DROPPED_PACKET).
messageParam(marker.getOriginalSendingIndex(), event.getPacketType()).
callerParam(event)
);
} catch (IOException e) {
// Just print the error
e.printStackTrace();
}
}
/**
* Automatically transmits every delayed packet.
*/
public void cleanupAll() {
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,228 @@
/*
* 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.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Queue;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
/**
* Synchronization views copied from Google Guava.
*
* @author Kristian
*/
class Synchronization {
/**
* Create a synchronized wrapper for the given queue.
* <p>
* This wrapper cannot synchronize the iterator(). Callers are expected
* to synchronize iterators manually.
* @param queue - the queue to synchronize.
* @param mutex - synchronization mutex, or NULL to use the queue.
* @return A synchronization wrapper.
*/
public static <E> Queue<E> queue(Queue<E> queue, @Nullable Object mutex) {
return (queue instanceof SynchronizedQueue) ?
queue :
new SynchronizedQueue<E>(queue, mutex);
}
private static class SynchronizedObject implements Serializable {
private static final long serialVersionUID = -4408866092364554628L;
final Object delegate;
final Object mutex;
SynchronizedObject(Object delegate, @Nullable Object mutex) {
this.delegate = Preconditions.checkNotNull(delegate);
this.mutex = (mutex == null) ? this : mutex;
}
Object delegate() {
return delegate;
}
// No equals and hashCode; see ForwardingObject for details.
@Override
public String toString() {
synchronized (mutex) {
return delegate.toString();
}
}
}
private static class SynchronizedCollection<E> extends SynchronizedObject implements Collection<E> {
private static final long serialVersionUID = 5440572373531285692L;
private SynchronizedCollection(Collection<E> delegate,
@Nullable Object mutex) {
super(delegate, mutex);
}
@SuppressWarnings("unchecked")
@Override
Collection<E> delegate() {
return (Collection<E>) super.delegate();
}
@Override
public boolean add(E e) {
synchronized (mutex) {
return delegate().add(e);
}
}
@Override
public boolean addAll(Collection<? extends E> c) {
synchronized (mutex) {
return delegate().addAll(c);
}
}
@Override
public void clear() {
synchronized (mutex) {
delegate().clear();
}
}
@Override
public boolean contains(Object o) {
synchronized (mutex) {
return delegate().contains(o);
}
}
@Override
public boolean containsAll(Collection<?> c) {
synchronized (mutex) {
return delegate().containsAll(c);
}
}
@Override
public boolean isEmpty() {
synchronized (mutex) {
return delegate().isEmpty();
}
}
@Override
public Iterator<E> iterator() {
return delegate().iterator(); // manually synchronized
}
@Override
public boolean remove(Object o) {
synchronized (mutex) {
return delegate().remove(o);
}
}
@Override
public boolean removeAll(Collection<?> c) {
synchronized (mutex) {
return delegate().removeAll(c);
}
}
@Override
public boolean retainAll(Collection<?> c) {
synchronized (mutex) {
return delegate().retainAll(c);
}
}
@Override
public int size() {
synchronized (mutex) {
return delegate().size();
}
}
@Override
public Object[] toArray() {
synchronized (mutex) {
return delegate().toArray();
}
}
@Override
public <T> T[] toArray(T[] a) {
synchronized (mutex) {
return delegate().toArray(a);
}
}
}
private static class SynchronizedQueue<E> extends SynchronizedCollection<E> implements Queue<E> {
private static final long serialVersionUID = 1961791630386791902L;
SynchronizedQueue(Queue<E> delegate, @Nullable Object mutex) {
super(delegate, mutex);
}
@Override
Queue<E> delegate() {
return (Queue<E>) super.delegate();
}
@Override
public E element() {
synchronized (mutex) {
return delegate().element();
}
}
@Override
public boolean offer(E e) {
synchronized (mutex) {
return delegate().offer(e);
}
}
@Override
public E peek() {
synchronized (mutex) {
return delegate().peek();
}
}
@Override
public E poll() {
synchronized (mutex) {
return delegate().poll();
}
}
@Override
public E remove() {
synchronized (mutex) {
return delegate().remove();
}
}
}
}
@@ -0,0 +1,246 @@
package com.comphenix.protocol.collections;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Ticker;
import com.google.common.collect.Maps;
import com.google.common.primitives.Longs;
/**
* Represents a hash map where each association may expire after a given time has elapsed.
* <p>
* Note that replaced key-value associations are only collected once the original expiration time has elapsed.
*
* @author Kristian Stangeland
*
* @param <K> - type of the keys.
* @param <V> - type of the values.
*/
public class ExpireHashMap<K, V> {
private class ExpireEntry implements Comparable<ExpireEntry> {
public final long expireTime;
public final K expireKey;
public final V expireValue;
public ExpireEntry(long expireTime, K expireKey, V expireValue) {
this.expireTime = expireTime;
this.expireKey = expireKey;
this.expireValue = expireValue;
}
@Override
public int compareTo(ExpireEntry o) {
return Longs.compare(expireTime, o.expireTime);
}
@Override
public String toString() {
return "ExpireEntry [expireTime=" + expireTime + ", expireKey=" + expireKey
+ ", expireValue=" + expireValue + "]";
}
}
private Map<K, ExpireEntry> keyLookup = new HashMap<K, ExpireEntry>();
private PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<ExpireEntry>();
// View of keyLookup with direct values
private Map<K, V> valueView = Maps.transformValues(keyLookup, new Function<ExpireEntry, V>() {
@Override
public V apply(ExpireEntry entry) {
return entry.expireValue;
}
});
// Supplied by the constructor
private Ticker ticker;
/**
* Construct a new hash map where each entry may expire at a given time.
*/
public ExpireHashMap() {
this(Ticker.systemTicker());
}
/**
* Construct a new hash map where each entry may expire at a given time.
* @param ticker - supplier of the current time.
*/
public ExpireHashMap(Ticker ticker) {
this.ticker = ticker;
}
/**
* Retrieve the value associated with the given key, if it has not expired.
* @param key - the key.
* @return The value, or NULL if not found or it has expired.
*/
public V get(K key) {
evictExpired();
ExpireEntry entry = keyLookup.get(key);
return entry != null ? entry.expireValue : null;
}
/**
* Associate the given key with the given value, until the expire delay have elapsed.
* @param key - the key.
* @param value - the value.
* @param expireDelay - the amount of time until this association expires. Must be greater than zero.
* @param expireUnit - the unit of the expiration.
* @return Any previously unexpired association with this key, or NULL.
*/
public V put(K key, V value, long expireDelay, TimeUnit expireUnit) {
Preconditions.checkNotNull(expireUnit, "expireUnit cannot be NULL");
Preconditions.checkState(expireDelay > 0, "expireDelay cannot be equal or less than zero.");
evictExpired();
ExpireEntry entry = new ExpireEntry(
ticker.read() + TimeUnit.NANOSECONDS.convert(expireDelay, expireUnit),
key, value
);
ExpireEntry previous = keyLookup.put(key, entry);
// We enqueue its removal
expireQueue.add(entry);
return previous != null ? previous.expireValue : null;
}
/**
* Determine if the given key is referring to an unexpired association in the map.
* @param key - the key.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean containsKey(K key) {
evictExpired();
return keyLookup.containsKey(key);
}
/**
* Determine if the given value is referring to an unexpired association in the map.
* @param value - the value.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean containsValue(V value) {
evictExpired();
// Linear scan is the best we've got
for (ExpireEntry entry : keyLookup.values()) {
if (Objects.equal(value, entry.expireValue)) {
return true;
}
}
return false;
}
/**
* Remove a key and its associated value from the map.
* @param key - the key to remove.
* @return Value of the removed association, NULL otherwise.
*/
public V removeKey(K key) {
evictExpired();
ExpireEntry entry = keyLookup.remove(key);
return entry != null ? entry.expireValue : null;
}
/**
* Retrieve the number of entries in the map.
* @return The number of entries.
*/
public int size() {
evictExpired();
return keyLookup.size();
}
/**
* Retrieve a view of the keys in the current map.
* @return View of the keys.
*/
public Set<K> keySet() {
evictExpired();
return keyLookup.keySet();
}
/**
* Retrieve a view of all the values in the current map.
* @return All the values.
*/
public Collection<V> values() {
evictExpired();
return valueView.values();
}
/**
* Retrieve a view of all the entries in the set.
* @return All the entries.
*/
public Set<Entry<K, V>> entrySet() {
evictExpired();
return valueView.entrySet();
}
/**
* Retrieve a view of this expire map as an ordinary map that does not support insertion.
* @return The map.
*/
public Map<K, V> asMap() {
evictExpired();
return valueView;
}
/**
* Clear all references to key-value pairs that have been removed or replaced before they were naturally evicted.
* <p>
* This operation requires a linear scan of the current entries in the map.
*/
public void collect() {
// First evict what we can
evictExpired();
// Recreate the eviction queue - this is faster than removing entries in the old queue
expireQueue.clear();
expireQueue.addAll(keyLookup.values());
}
/**
* Clear all the entries in the current map.
*/
public void clear() {
keyLookup.clear();
expireQueue.clear();
}
/**
* Evict any expired entries in the map.
* <p>
* This is called automatically by any of the read or write operations.
*/
protected void evictExpired() {
long currentTime = ticker.read();
// Remove expired entries
while (expireQueue.size() > 0 && expireQueue.peek().expireTime <= currentTime) {
ExpireEntry entry = expireQueue.poll();
if (entry == keyLookup.get(entry.expireKey)) {
keyLookup.remove(entry.expireKey);
}
}
}
@Override
public String toString() {
return valueView.toString();
}
}
@@ -0,0 +1,140 @@
package com.comphenix.protocol.collections;
import java.util.Arrays;
import java.util.Map;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
/**
* Represents a very quick integer-based lookup map, with a fixed key space size.
* <p>
* Integers must be non-negative.
* @author Kristian
*/
public class IntegerMap<T> {
private T[] array;
private int size;
/**
* Construct a new integer map.
* @param <T> Parameter type
* @return A new integer map.
*/
public static <T> IntegerMap<T> newMap() {
return new IntegerMap<T>();
}
/**
* Construct a new integer map with a default capacity.
*/
public IntegerMap() {
this(8);
}
/**
* Construct a new integer map with a given capacity.
* @param initialCapacity - the capacity.
*/
public IntegerMap(int initialCapacity) {
@SuppressWarnings("unchecked")
T[] backingArray = (T[]) new Object[initialCapacity];
this.array = backingArray;
this.size = 0;
}
/**
* Associate an integer key with the given value.
* @param key - the integer key. Cannot be negative.
* @param value - the value. Cannot be NULL.
* @return The previous association, or NULL if not found.
*/
public T put(int key, T value) {
ensureCapacity(key);
T old = array[key];
array[key] = Preconditions.checkNotNull(value, "value cannot be NULL");
if (old == null)
size++;
return old;
}
/**
* Remove an association from the map.
* @param key - the key of the association to remove.
* @return The old associated value, or NULL.
*/
public T remove(int key) {
T old = array[key];
array[key] = null;
if (old != null)
size--;
return old;
}
/**
* Resize the backing array to fit the given key.
* @param key - the key.
*/
protected void ensureCapacity(int key) {
int newLength = array.length;
// Don't resize if the key fits
if (key < 0)
throw new IllegalArgumentException("Negative key values are not permitted.");
if (key < newLength)
return;
while (newLength <= key) {
int next = newLength * 2;
// Handle overflow
newLength = next > newLength ? next : Integer.MAX_VALUE;
}
this.array = Arrays.copyOf(array, newLength);
}
/**
* Retrieve the number of mappings in this map.
* @return The number of mapping.
*/
public int size() {
return size;
}
/**
* Retrieve the value associated with a given key.
* @param key - the key.
* @return The value, or NULL if not found.
*/
public T get(int key) {
if (key >= 0 && key < array.length)
return array[key];
return null;
}
/**
* Determine if the given key exists in the map.
* @param key - the key to check.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean containsKey(int key) {
return get(key) != null;
}
/**
* Convert the current map to an Integer map.
* @return The Integer map.
*/
public Map<Integer, Object> toMap() {
Map<Integer, Object> map = Maps.newHashMap();
for (int i = 0; i < array.length; i++) {
if (array[i] != null) {
map.put(i, array[i]);
}
}
return map;
}
}
@@ -0,0 +1,144 @@
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.List;
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;
/**
* A thread-safe implementation of a listener multimap.
*
* @author Kristian
*/
public abstract class AbstractConcurrentListenerMultimap<TListener> {
// The core of our map
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 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 (PacketType type : whitelist.getTypes()) {
addListener(type, prioritized);
}
}
// Add the listener to a specific packet notifcation list
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>>();
// 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;
}
}
// Thread safe
list.add(listener);
}
/**
* Removes the given listener from the packet event list.
* @param listener - listener to remove.
* @param whitelist - the packet whitelist that was used.
* @return Every packet ID that was removed due to no listeners.
*/
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 (PacketType type : whitelist.getTypes()) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
// Remove any listeners
if (list != null) {
// Don't remove from newly created lists
if (list.size() > 0) {
// Remove this listener. Note that priority is generally ignored.
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
if (list.size() == 0) {
mapListeners.remove(type);
removedPackets.add(type);
}
}
}
// Move on to the next
}
return removedPackets;
}
/**
* 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 type - packet type.
* @return Registered listeners.
*/
public Collection<PrioritizedListener<TListener>> getListener(PacketType type) {
return mapListeners.get(type);
}
/**
* Retrieve every listener.
* @return Every listener.
*/
public Iterable<PrioritizedListener<TListener>> values() {
return Iterables.concat(mapListeners.values());
}
/**
* Retrieve every registered packet type:
* @return Registered packet type.
*/
public Set<PacketType> keySet() {
return mapListeners.keySet();
}
/**
* Remove all packet listeners.
*/
protected void clearListeners() {
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,330 @@
package com.comphenix.protocol.concurrency;
import com.comphenix.protocol.utility.SafeCacheBuilder;
import com.comphenix.protocol.utility.Util;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.RemovalListener;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.bukkit.entity.Player;
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;
/**
* 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 == null ? null : player.getAddress();
}
},
/**
* Use a player's {@link Player#getName()} as key in the map.
*/
NAME {
@Override
public Object apply(Player player) {
return player == null ? null : player.getName();
}
},
}
/**
* An internal map of player keys to values.
*/
private ConcurrentMap<Object, TValue> valueLookup = createValueMap();
/**
* A cache of the associated keys for each player.
*/
private ConcurrentMap<Object, Player> keyLookup = createKeyCache();
/**
* The method used to retrieve a unique key for a player.
*/
private 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.
*/
private 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.
*/
private ConcurrentMap<Object, TValue> createValueMap() {
return Maps.newConcurrentMap();
}
/**
* Construct a cache of keys and the associated player.
* @return The key map.
*/
private ConcurrentMap<Object, Player> createKeyCache() {
return SafeCacheBuilder.newBuilder().
weakValues().
removalListener(
(RemovalListener<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.
*/
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.
*/
private 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.
*/
private 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.
*/
private Object cachePlayerKey(Player player) {
Preconditions.checkNotNull(player, "player cannot be null");
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();
}
}
@@ -0,0 +1,244 @@
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Objects;
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 Collection<T> {
// Prevent reordering
private volatile List<T> list;
/**
* Construct an empty sorted array.
*/
public SortedCopyOnWriteArray() {
list = new ArrayList<T>();
}
/**
* Create a sorted array from the given list. The elements will be automatically sorted.
* @param wrapped - the collection whose elements are to be placed into the list.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped) {
this.list = new ArrayList<T>(wrapped);
}
/**
* Create a sorted array from the given list.
* @param wrapped - the collection whose elements are to be placed into the list.
* @param sort - TRUE to automatically sort the collection, FALSE if it is already sorted.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped, boolean sort) {
this.list = new ArrayList<T>(wrapped);
if (sort) {
Collections.sort(list);
}
}
/**
* Inserts the given element in the proper location.
* @param value - element to insert.
*/
@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");
List<T> copy = new ArrayList<T>();
for (T element : list) {
// If the value is now greater than the current element, it should be placed right before it
if (value != null && value.compareTo(element) < 0) {
copy.add(value);
value = null;
}
copy.add(element);
}
// Don't forget to add it
if (value != null)
copy.add(value);
list = copy;
return true;
}
@Override
public synchronized boolean addAll(Collection<? extends T> values) {
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
// Insert the new content and sort it
copy.addAll(list);
copy.addAll(values);
Collections.sort(copy);
list = copy;
return true;
}
/**
* Removes from the list by making a new list with every element except the one given.
* <p>
* Objects will be compared using the given objects equals() method.
* @param value - value to remove.
*/
@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 (!Objects.equal(value, element)) {
copy.add(element);
} else {
result = true;
}
}
list = copy;
return result;
}
@Override
public boolean removeAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
copy.removeAll(values);
list = copy;
return true;
}
@Override
public boolean retainAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
copy.removeAll(values);
list = copy;
return true;
}
/**
* Removes from the list by making a copy of every element except the one with the given index.
* @param index - index of the element to remove.
*/
public synchronized void remove(int index) {
List<T> copy = new ArrayList<T>(list);
copy.remove(index);
list = copy;
}
/**
* Retrieves an element by index.
* @param index - index of element to retrieve.
* @return The element at the given location.
*/
public T get(int index) {
return list.get(index);
}
/**
* Retrieve the size of the list.
* @return Size of the list.
*/
public int size() {
return list.size();
}
/**
* Retrieves an iterator over the elements in the given list.
* Warning: No not attempt to remove elements using the iterator.
*/
public Iterator<T> iterator() {
return Iterables.unmodifiableIterable(list).iterator();
}
@Override
public void clear() {
list = new ArrayList<T>();
}
@Override
public boolean contains(Object value) {
return list.contains(value);
}
@Override
public boolean containsAll(Collection<?> values) {
return list.containsAll(values);
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public Object[] toArray() {
return list.toArray();
}
@SuppressWarnings("hiding")
@Override
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,147 @@
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 sender - 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, true)
.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;
}
@@ -0,0 +1,68 @@
/*
* 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;
/**
* Represents a packet event priority, similar to the Bukkit EventPriority.
*
* @author Kristian
*/
public enum ListenerPriority {
/**
* Event call is of very low importance and should be ran first, to allow
* other plugins to further customise the outcome.
*/
LOWEST(0),
/**
* Event call is of low importance.
*/
LOW(1),
/**
* Event call is neither important or unimportant, and may be ran normally.
*/
NORMAL(2),
/**
* Event call is of high importance.
*/
HIGH(3),
/**
* Event call is critical and must have the final say in what happens to the
* event.
*/
HIGHEST(4),
/**
* Event is listened to purely for monitoring the outcome of an event.
* <p>
* No modifications to the event should be made under this priority.
*/
MONITOR(5);
private final int slot;
private ListenerPriority(int slot) {
this.slot = slot;
}
/**
* A low slot represents a low priority.
* @return Integer representation of this priorty.
*/
public int getSlot() {
return slot;
}
}
@@ -0,0 +1,479 @@
/*
* 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 "ListeningWhitelist[priority=" + priority + ", packets=" + types + ", gamephase=" + gamePhase + ", options=" + options + "]";
}
/**
* 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);
}
}
}
@@ -0,0 +1,96 @@
/*
* 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;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.Packets;
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 received packet.
*
* @author Kristian
*/
public abstract class MonitorAdapter implements PacketListener {
private Plugin plugin;
private ListeningWhitelist sending = ListeningWhitelist.EMPTY_WHITELIST;
private ListeningWhitelist receiving = ListeningWhitelist.EMPTY_WHITELIST;
public MonitorAdapter(Plugin plugin, ConnectionSide side) {
initialize(plugin, side, getLogger(plugin));
}
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
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 = ListeningWhitelist.newBuilder().monitor().types(PacketRegistry.getServerPacketTypes()).gamePhaseBoth().build();
if (side.isForClient())
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);
if (side.isForClient())
this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getRegistry().values(), GamePhase.BOTH);
logger.log(Level.WARNING, "Defaulting to 1.3 packets.", e);
}
}
/**
* Retrieve a logger, even if we're running in a CraftBukkit version that doesn't support it.
* @param plugin - the plugin to retrieve.
* @return The logger.
*/
private Logger getLogger(Plugin plugin) {
try {
return plugin.getLogger();
} catch (NoSuchMethodError e) {
return Logger.getLogger("Minecraft");
}
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sending;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receiving;
}
@Override
public Plugin getPlugin() {
return plugin;
}
}
@@ -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,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.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.comphenix.protocol.injector.server.TemporaryPlayer;
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.create());
/**
* Automatically generated by Eclipse.
*/
private static final long serialVersionUID = -5360289379097430620L;
private transient WeakReference<Player> playerReference;
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);
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.getPlayerReference();
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;
}
private WeakReference<Player> getPlayerReference() {
Player player = playerReference.get();
if (player instanceof TemporaryPlayer) {
Player updated = player.getPlayer();
if (updated != null && !(updated instanceof TemporaryPlayer)) {
playerReference.clear();
playerReference = new WeakReference<>(updated);
}
}
return playerReference;
}
/**
* Retrieves the player that has sent the packet or is receiving it.
* @return The player associated with this event.
*/
public Player getPlayer() {
return getPlayerReference().get();
}
/**
* Whether or not the player in this PacketEvent is temporary (i.e. they don't have a true player instance yet).
* Temporary players have a limited subset of methods that may be used:
* <ul>
* <li>getPlayer</li>
* <li>getAddress</li>
* <li>getServer</li>
* <li>chat</li>
* <li>sendMessage</li>
* <li>kickPlayer</li>
* <li>isOnline</li>
* </ul>
*
* Anything else will throw an UnsupportedOperationException. Use this check before calling other methods when
* dealing with packets early in the login sequence or if you get the aforementioned exception.
*
* @return True if the player is temporary, false if not.
*/
public boolean isPlayerTemporary() {
return getPlayer() instanceof TemporaryPlayer;
}
/**
* 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)
Player player = getPlayer();
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : 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
Player offlinePlayer = serialized.getPlayer();
playerReference = new WeakReference<>(offlinePlayer);
}
}
@Override
public String toString() {
return "PacketEvent[player=" + getPlayer() + ", packet=" + packet + "]";
}
}
@@ -0,0 +1,71 @@
/*
* 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 org.bukkit.plugin.Plugin;
/**
* 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
*/
public interface PacketListener {
/**
* Invoked right before a packet is transmitted from the server to the client.
* <p>
* Note that the packet may be replaced, if needed.
* <p>
* 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 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}
* if you need to call the Bukkit API.
* @param event - the packet that has been received.
*/
public void onPacketReceiving(PacketEvent event);
/**
* Retrieve which packets sent by the server this listener will observe.
* @return List of server packets to observe, along with the priority.
*/
public ListeningWhitelist getSendingWhitelist();
/**
* Retrieve which packets sent by the client this listener will observe.
* @return List of server packets to observe, along with the priority.
*/
public ListeningWhitelist getReceivingWhitelist();
/**
* Retrieve the plugin that created list packet listener.
* @return The plugin, or NULL if not available.
*/
public Plugin getPlugin();
}
@@ -0,0 +1,142 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2017 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.events;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.commons.lang.Validate;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Stores and retrieves metadata for applicable packet objects.
* @author dmulloy2
*/
class PacketMetadata {
private static class MetaObject<T> {
private final String key;
private final T value;
private MetaObject(String key, T value) {
this.key = key;
this.value = value;
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o instanceof MetaObject) {
MetaObject that = (MetaObject) o;
return that.key.equals(this.key) &&
that.value.equals(this.value);
}
return false;
}
@Override
public String toString() {
return "MetaObject[" + key + "=" + value + "]";
}
}
// Packet meta cache
private static Cache<Object, List<MetaObject>> META_CACHE;
public static <T> Optional<T> get(Object packet, String key) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
return Optional.empty();
}
List<MetaObject> meta = META_CACHE.getIfPresent(packet);
if (meta == null) {
return Optional.empty();
}
for (MetaObject object : meta) {
if (object.key.equals(key)) {
return Optional.of((T) object.value);
}
}
return Optional.empty();
}
private static void createCache() {
META_CACHE = CacheBuilder
.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
}
public static <T> void set(Object packet, String key, T value) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
createCache();
}
List<MetaObject> packetMeta;
try {
packetMeta = META_CACHE.get(packet, ArrayList::new);
} catch (ExecutionException ex) {
// Not possible, but let's humor the array list constructor having an issue
packetMeta = new ArrayList<>();
}
packetMeta.removeIf(meta -> meta.key.equals(key));
packetMeta.add(new MetaObject<>(key, value));
META_CACHE.put(packet, packetMeta);
}
public static <T> Optional<T> remove(Object packet, String key) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
return Optional.empty();
}
List<MetaObject> packetMeta = META_CACHE.getIfPresent(packet);
if (packetMeta == null) {
return Optional.empty();
}
Optional<T> value = Optional.empty();
Iterator<MetaObject> iter = packetMeta.iterator();
while (iter.hasNext()) {
MetaObject meta = iter.next();
if (meta.key.equals(key)) {
value = Optional.of((T) meta.value);
iter.remove();
}
}
return value;
}
}
@@ -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);
}
Loaded 100 of 358 files, more files were not shown because too many files have changed in this diff. Show more