Compare commits

..
176 Commits
Author SHA1 Message Date
Dan Mulloy ab0faab396 Update version to 4.6.0 for release 2021-02-15 13:59:14 -05:00
Dan Mulloy 765fd9e987 Broaden throwable for misbehaving metrics plugins (#1078)
Fixes #1078
2021-02-03 09:39:04 -05:00
Dan Mulloy 8f7b530613 Tested with 1.16.5 2021-01-24 17:14:32 -05:00
Dan Mulloy 30fe81d366 Move getPacketDescription from the command to HexDumper as a public API
Related to filoghost/HolographicDisplays#385
2021-01-04 13:35:58 -05:00
PimvanderLoos b54dd49426 Replace CGLib with ByteBuddy (#984)
- The gclib dependency in the EnchancerFactory has been removed. All classes that used the actual factory part of it have been updated to use bytebuddy instead. This class will have to be removed at some point, but at the moment it is still used for accessing its class loader.
- Renamed EnhancerFactory to ByteBuddyFactory. All ByteBuddy actions should go through this now. Every subclass created here implements the ByteBuddyGenerated interface. This makes it possible to recognize classes generated using ByteBuddy (by default, it doesn't leave such a trace).
- Removed the method DefaultInstances#forEnhancer(Enhancer). This method isn't used anywhere; the last trace of usage of the method I could find was in 2013 (in the NetworkServerInjector). External plugins (I couldn't find any that used it), they should really have their own implementation, given that they already require an instance of an Enchancer. As such, I feel it is safe to remove rather than update it.
2021-01-04 00:24:34 -05:00
Dan Mulloy 26274fed52 ✨backwards compat✨ 2021-01-02 23:59:13 -05:00
Jan Lindner aa555f792e add sound converter for versions greater 1.16.4 (#1027) 2021-01-02 23:55:23 -05:00
Dan Mulloy 0d50905754 Merge branch 'master' of https://github.com/dmulloy2/ProtocolLib 2021-01-02 23:19:28 -05:00
Dan Mulloy b7132196fb Fix ChatExtensions exception
Fixes #1045
2021-01-02 23:19:20 -05:00
Dan Mulloy eb54b99856 Update funding info 2020-12-30 15:31:39 -06:00
Dan Mulloy 13b3d8679d Update ChatExtensions for 1.16.4
Fixes #1044
2020-12-27 00:04:11 -05:00
dependabot[bot] 1545a88e06 Bump junit from 4.13 to 4.13.1 (#989)
Bumps [junit](https://github.com/junit-team/junit4) from 4.13 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.13...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-23 14:59:46 -05:00
Thibaut Gautier 2b22999b94 Fixed "protocollib_encoder" attempting to read non-nms packets (#959)
Added an override to the encoder as such would process any non-wire packets as NMS packets. In the case of the use of different APIs, such as Artemis Packet API, such would cause a conflict and would spit out casting exceptions. It is quite easy to resolve by exclusively accepting Wire Packets and packets which are assignable to the packet class. This solves the issue and tada happy ending.
2020-12-23 14:59:07 -05:00
PimvanderLoosandMark Vainomaa bbb053aa4e Fix Java 15 (#1025)
- Implemented a fix for the incompatibility with Java 15. This incompatibility was caused by the fact that the lambda generated in the NMS.NetworkManager is a hidden class in J15. Starting in Java 15, final fields in hidden classes can no longer be modified regardless of the 'accessible' flag. (see https://openjdk.java.net/jeps/371 "Using a hidden class", point 3). To circumvent this issue, this retrieves the data from the existing fields in the hidden class (a runnable or a callable) other than the packet. It then retrieves the constructor of the hidden class and instantiates it using the previously-retrieved data and the modified packet instance (this code is only used if the packet instance changed).
- Introduced a new ObjectReconstructor class that does all the fields/constructor discovering/accessing etc. The Runnable and Callable methods each get one instance of this class so that we can avoid having to get the fields/constructors and set them accessible every time we want to replace a packet.

Co-authored-by: Mark Vainomaa <mikroskeem@mikroskeem.eu>
2020-12-23 14:57:16 -05:00
Aurora 7bac4ec634 Update to 1.16.4 (#1007) 2020-11-09 15:11:48 -05:00
Dan Mulloy 4bc9e8b7b7 Fix a class cast exception with array wrappers
(Kinda surprised there isn't an issue to link here)
2020-10-19 16:30:42 -04:00
Dan Mulloy f381f0a2f7 Fix multi block change cloning
Fixes #990
2020-10-19 11:04:11 -04:00
Dan Mulloy 553e4b6813 Update to 1.16.3 2020-10-19 11:02:32 -04:00
Dan Mulloy bdaa843f2d Update to 1.16.2
Fixes #941
Fixes #944
Fixes #934
2020-08-24 13:19:49 -04:00
Dan Mulloy 13f5c14599 Fix bytecode breakage with StructureModifier
Fixes #935
2020-08-24 11:40:47 -04:00
Dan Mulloy b871eb3d54 Fix data watcher cloning in 1.16
Fixes #925
2020-08-06 14:26:27 -04:00
Dan Mulloy 12e3a895b3 Remove stray debug 2020-08-06 13:54:16 -04:00
Dan Mulloy 8c5fbe3298 Merge remote-tracking branch 'origin/master' 2020-08-04 19:04:22 -04:00
Dan Mulloy 0512215007 Add modifiers for world keys and moving block position
Fixes #906
Fixes #927
2020-08-04 19:03:59 -04:00
Tarrant 80f4c7b9a7 Support for MerchantRecipeList (#894)
* Added support for MerchantRecipeList found in OPEN_WINDOW_MERCHANT of MC versions 1.13+
2020-07-26 19:33:05 -04:00
Dan Mulloy 8c51b175c4 Merge remote-tracking branch 'origin/master' 2020-07-05 23:47:25 -04:00
Dan Mulloy 8d991ad5a7 Support dimensions in 1.16 (#893)
Fixes #893
2020-07-05 23:47:06 -04:00
Dan Mulloy f19bfc613e Add support for entity equipment pair in 1.16 2020-07-05 13:29:52 -04:00
Lewys Davies a7e702899a Implementing EntityPose (#873) 2020-06-30 23:18:04 -04:00
RERERE deb192b04d Support 1.16 PlayerDigType enumeration variable change (#889) 2020-06-30 23:16:52 -04:00
Dan Mulloy fbe46f7bac Force all chat packets to have empty UUID 2020-06-29 20:35:41 -04:00
Dan Mulloy fd93c1c553 Try to fix reload, give more detailed error (#874)
Fixes #874
2020-06-28 17:06:12 -04:00
Dan Mulloy b2f6a56843 Update tile entities for 1.16 (#880)
Ended up being simpler than I thought
2020-06-28 17:02:22 -04:00
Dan Mulloy 658da31d46 Initial support for 1.16 (#880)
Note that things may not initially work as expected. There are known issues (see #880) relating to dimensions, chat packets, and tile entities. There were also internal changes to attributes, though hopefully those are fixed. As always, report issues on GitHub.
2020-06-28 15:59:30 -04:00
xxDark 77feaa857e Cache perm gen bean (#876) 2020-06-23 16:26:30 -04:00
Niklas Seyfarth 5f204d798c StreamSerializer: Reduce duplicated code and allow (de)serialization to and from byte arrays. (#875) 2020-06-21 12:58:37 -04:00
NewbieOrange 5183bd53b5 Add MCP packet names for Bukkit-Forge hybrid server (#862)
Adds mappings to support MCP packets

Fixes #858
2020-06-16 13:35:38 -04:00
Dan Mulloy fdd30a7b87 Remove a bunch of legacy (<1.8) code
Shouldn't break any servers running 1.8+, but this version is all about code cleanup baby
2020-06-06 15:13:29 -04:00
Dan Mulloy 7ac4ac696f Fold BukkitExecutors into ProtocolLib (#721)
Fixes #721
2020-06-06 13:49:26 -04:00
Dan Mulloy c203fda391 Update version to 4.6.0-SNAPSHOT for development 2020-06-06 13:38:11 -04:00
Dan Mulloy 9a65ddbc43 Update version to 4.5.1 for release 2020-06-03 21:21:31 -04:00
Dan Mulloy 54c252a354 Improve WrappedDataWatcher hasIndex performance
Use the map to check for indices instead of getting the object

Addresses #850
2020-05-27 21:56:22 -04:00
Dan Mulloy e92abda187 Try to fix forge compatibility
Addresses #825
2020-05-27 21:41:56 -04:00
Dan Mulloy 3f7b7f4bb3 Make sure all Spigot forks are included for updater purposes
Addresses #835
2020-05-24 20:35:25 -04:00
Andrew Steinborn 944b3f8280 Cache PacketType#hashCode (#818)
This should result in a general performance improvement, as PacketType is often used as map keys across ProtocolLib.
2020-05-24 15:45:42 -04:00
Dan Mulloy b04fca8324 Fix packet interception error with ViaVersion (#724)
Thanks to @KennyTV and @MedicOP for their help in tracking this bug down. Essentially the decode method we were using could be different depending on when the player logged in, which clashed with PL's static handling of it.

Fixes #724
Fixes #791
Fixes #803
Fixes #811
Fixes #813
Fixes #819
...and probably some others...
2020-05-24 15:29:01 -04:00
Dan Mulloy bfa0eee91e Fix direction and dimension enums 2020-05-03 15:59:55 -04:00
Dan Mulloy 308e3d3417 Update to 1.15.2 2020-05-03 13:15:42 -04:00
Dan Mulloy 42e48aa9b8 Be more specific in which sendPacket method we choose
Related to PaperMC/Paper#3282
2020-05-03 12:44:59 -04:00
Dan Mulloy 18d0193288 Merge remote-tracking branch 'origin/master' 2020-04-30 18:47:06 -04:00
Dan Mulloy 0ab552a6ef Fix issue with legacy block data (#809)
Fixes #809
2020-04-30 18:46:29 -04:00
Dan Mulloy d9fef94bc6 Merge pull request #782 from bergerkiller/master
Replace ThreadLocal scheduleProcessPackets with queue, fixes #763
2020-03-03 09:15:24 -05:00
Irmo van den Berge ab5fb40f8f Replace ThreadLocal scheduleProcessPackets with queue, fixes #763 2020-03-01 13:07:14 +01:00
libraryaddict 4baa4aa724 AttributeModifier$Operation only to be fetched if viable (#773) 2020-02-18 16:12:24 -05:00
Dan Mulloy 7a4a285935 Add some more useful metrics 2020-01-11 13:49:36 -05:00
Dan Mulloy f38c393d6f Use new Spigot updater
Fixes #746
2020-01-11 13:47:49 -05:00
Dan Mulloy 22c2a4abcc Fix compatibility with CraftBukkit
Still recommend Spigot, for what it's worth
2019-12-26 11:39:23 -05:00
Dan Mulloy 1a434e9ea2 Try changing visibility on ProtocolLogger#init
Addresses #740
2019-12-26 11:34:33 -05:00
Dan Mulloy 25a5f1d07f Update version to 4.5.1-SNAPSHOT 2019-12-26 11:28:14 -05:00
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
357 changed files with 13899 additions and 19085 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
github: [dmulloy2]
custom: ["https://paypal.me/dmulloy2"]
+2 -1
View File
@@ -4,7 +4,8 @@ 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:
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.
+5 -1
View File
@@ -8,7 +8,7 @@
.settings/
bin/
.idea/
.iml
*.iml
.ipr
.iws
@@ -175,4 +175,8 @@ pip-log.txt
.DS_Store
# Log4J files
logs/
.gradle/
build/
logs/
+2 -4
View File
@@ -1,13 +1,11 @@
language: java
jdk:
- oraclejdk8
- oraclejdk7
- openjdk8
script: mvn clean test
#before_install: cd ProtocolLib
install: true
notifications:
email:
recipients:
- "dmulloy2@live.com"
on_success: change
on_failure: always
on_failure: always
+169 -163
View File
@@ -1,163 +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](http://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](http://www.spigotmc.org/).
### Resources
* [Resource Page](http://www.spigotmc.org/resources/protocollib.1997/)
* [Dev Builds](http://ci.dmulloy2.net/job/ProtocolLib)
* [JavaDoc](http://ci.dmulloy2.net/job/ProtocolLib/javadoc)
### Compilation
ProtocolLib is built with Maven and requires Spigot and SpigotAPI, which can be found [here](http://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>http://repo.dmulloy2.net/content/groups/public/</url>
</repository>
...
</repositories>
<dependencies>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>3.6.5</version>
</dependency>
</dependencies>
````
Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go.
````java
private ProtocolManager protocolManager;
public void onLoad() {
protocolManager = ProtocolLibrary.getProtocolManager();
}
````
To listen for packets sent by the server to a client, add a server-side listener:
````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.
### Incompatibility
The following plugins (to be expanded) are not compatible with ProtocolLib:
* TagAPI
# 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/repository/public/</url>
</repository>
...
</repositories>
<dependencies>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.6.0</version>
</dependency>
</dependencies>
````
Or use the maven dependency with gradle:
```gradle
repositories {
maven { url "https://repo.dmulloy2.net/repository/public/" }
}
dependencies {
compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.6.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.
@@ -14,8 +14,8 @@
<parent>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib-Parent</artifactId>
<version>v3-SNAPSHOT</version>
<relativePath>../</relativePath>
<version>v4-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<repositories>
@@ -42,8 +42,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
@@ -77,20 +77,6 @@
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>release-sign-artifacts</id>
@@ -178,7 +178,7 @@ public final class Reflection {
}
/**
* Search for the first publically and privately defined method of the given name and parameter count.
* 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.
@@ -191,7 +191,7 @@ public final class Reflection {
}
/**
* Search for the first publically and privately defined method of the given name and parameter count.
* 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.
@@ -204,7 +204,7 @@ public final class Reflection {
}
/**
* Search for the first publically and privately defined method of the given name and parameter count.
* 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.
@@ -105,8 +105,7 @@ public abstract class TinyProtocol {
} catch (IllegalArgumentException ex) {
// Damn you, late bind
plugin.getLogger().info("[TinyProtocol] Delaying server channel injection due to late bind.");
// Damn you, late bind
new BukkitRunnable() {
@Override
public void run() {
@@ -129,7 +128,7 @@ public abstract class TinyProtocol {
synchronized (networkManagers) {
// Stop injecting channels
if (!closed) {
injectChannelInternal(channel);
channel.eventLoop().submit(() -> injectChannelInternal(channel));
}
}
} catch (Exception e) {
@@ -256,13 +255,12 @@ public abstract class TinyProtocol {
* <p>
* Note that this is not executed on the main thread.
*
* @param reciever - the receiving player, NULL for early login/status packets.
* @param receiver - the receiving player, NULL for early login/status packets.
* @param channel - the channel that received the packet. Never NULL.
* @param remoteAddress - remote address of the sending client. Never NULL.
* @param packet - the packet being sent.
* @return The packet to send instead, or NULL to cancel the transmission.
*/
public Object onPacketOutAsync(Player reciever, Channel channel, Object packet) {
public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) {
return packet;
}
@@ -283,7 +281,7 @@ public abstract class TinyProtocol {
/**
* Send a packet to a particular player.
* <p>
* Note that {@link #onPacketOutAsync(Player, Object)} will be invoked with this packet.
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param player - the destination player.
* @param packet - the packet to send.
@@ -295,7 +293,7 @@ public abstract class TinyProtocol {
/**
* Send a packet to a particular client.
* <p>
* Note that {@link #onPacketOutAsync(Player, Object)} will be invoked with this packet.
* 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.
@@ -307,7 +305,7 @@ public abstract class TinyProtocol {
/**
* Pretend that a given packet has been received from a player.
* <p>
* Note that {@link #onPacketInAsync(Player, Object)} will be invoked with this packet.
* 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.
@@ -319,7 +317,7 @@ public abstract class TinyProtocol {
/**
* Pretend that a given packet has been received from a given client.
* <p>
* Note that {@link #onPacketInAsync(Player, Object)} will be invoked with this packet.
* 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.
@@ -353,7 +351,7 @@ public abstract class TinyProtocol {
/**
* Add a custom channel handler to the given channel.
*
* @param player - the channel to inject.
* @param channel - the channel to inject.
* @return The intercepted channel, or NULL if it has already been injected.
*/
public void injectChannel(Channel channel) {
@@ -363,7 +361,7 @@ public abstract class TinyProtocol {
/**
* Add a custom channel handler to the given channel.
*
* @param player - the channel to inject.
* @param channel - the channel to inject.
* @return The packet interceptor.
*/
private PacketInterceptor injectChannelInternal(Channel channel) {
-252
View File
@@ -1,252 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>ProtocolLib-API</artifactId>
<name>ProtocolLib-API</name>
<version>4.2.1</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>http://www.spigotmc.org/resources/protocollib.1997/</url>
<packaging>jar</packaging>
<parent>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib-Parent</artifactId>
<version>v4-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.number></project.build.number>
<project.fullVersion>${project.version}</project.fullVersion>
</properties>
<build>
<defaultGoal>clean install</defaultGoal>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifestEntries>
<Main-Class>com.comphenix.protocol.Application</Main-Class>
<Implementation-Title>ProtocolLib</Implementation-Title>
<Implementation-Version>${project.fullVersion}</Implementation-Version>
<Implementation-Vendor>dmulloy2</Implementation-Vendor>
</manifestEntries>
</archive>
<finalName>ProtocolLib</finalName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.10</version>
<configuration>
<systemProperties>
<property>
<name>projectVersion</name>
<value>${project.version}</value>
</property>
</systemProperties>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>jenkins</id>
<activation>
<property>
<name>env.BUILD_NUMBER</name>
</property>
</activation>
<properties>
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
<project.fullVersion>${project.version}${project.build.number}</project.fullVersion>
</properties>
</profile>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<properties>
<project.fullVersion>${project.version}</project.fullVersion>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- TODO: Look into signing releases
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
-->
</plugins>
</build>
</profile>
</profiles>
<scm>
<connection>scm:git:git://github.com/dmulloy2/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:dmulloy2/ProtocolLib.git</developerConnection>
<url>https://github.com/dmulloy2/ProtocolLib</url>
</scm>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dmulloy2</id>
<name>Dan Mulloy</name>
<url>http://dmulloy2.net/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
</roles>
</developer>
<developer>
<id>aadnk</id>
<name>Kristian S. Stangeland</name>
<email>kr_stang@hotmail.com</email>
<url>http://comphenix.net/</url>
<roles>
<role>former author</role>
</roles>
<timezone>1</timezone>
</developer>
</developers>
<dependencies>
<!-- Included with Minecraft
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.23.Final</version>
</dependency>
-->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.11.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.11.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.comphenix.executors</groupId>
<artifactId>BukkitExecutors</artifactId>
<version>1.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -1,300 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import 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);
}
}
@@ -1,243 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.events;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.Player;
import com.comphenix.protocol.utility.EnhancerFactory;
/**
* Represents a player object that can be serialized by Java.
*
* @author Kristian
*/
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = -2728976288470282810L;
private transient Location bedSpawnLocation;
// Relevant data about an offline player
private String name;
private UUID uuid;
private long firstPlayed;
private long lastPlayed;
private boolean operator;
private boolean banned;
private boolean playedBefore;
private boolean online;
private boolean whitelisted;
// Proxy helper
private static Map<String, Method> lookup = new ConcurrentHashMap<String, Method>();
/**
* Constructor used by serialization.
*/
public SerializedOfflinePlayer() {
// Do nothing
}
/**
* Initialize this serializable offline player from another player.
* @param offline - another player.
*/
public SerializedOfflinePlayer(OfflinePlayer offline) {
this.name = offline.getName();
this.uuid = offline.getUniqueId();
this.firstPlayed = offline.getFirstPlayed();
this.lastPlayed = offline.getLastPlayed();
this.operator = offline.isOp();
this.banned = offline.isBanned();
this.playedBefore = offline.hasPlayedBefore();
this.online = offline.isOnline();
this.whitelisted = offline.isWhitelisted();
}
@Override
public boolean isOp() {
return operator;
}
@Override
public void setOp(boolean operator) {
this.operator = operator;
}
@Override
public Map<String, Object> serialize() {
throw new UnsupportedOperationException();
}
@Override
public Location getBedSpawnLocation() {
return bedSpawnLocation;
}
@Override
public long getFirstPlayed() {
return firstPlayed;
}
@Override
public long getLastPlayed() {
return lastPlayed;
}
@Override
public UUID getUniqueId() {
return uuid;
}
@Override
public String getName() {
return name;
}
@Override
public boolean hasPlayedBefore() {
return playedBefore;
}
@Override
public boolean isBanned() {
return banned;
}
@Override
public void setBanned(boolean banned) {
this.banned = banned;
}
@Override
public boolean isOnline() {
return online;
}
@Override
public boolean isWhitelisted() {
return whitelisted;
}
@Override
public void setWhitelisted(boolean whitelisted) {
this.whitelisted = whitelisted;
}
private void writeObject(ObjectOutputStream output) throws IOException {
output.defaultWriteObject();
// Serialize the bed spawn location
output.writeUTF(bedSpawnLocation.getWorld().getName());
output.writeDouble(bedSpawnLocation.getX());
output.writeDouble(bedSpawnLocation.getY());
output.writeDouble(bedSpawnLocation.getZ());
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
input.defaultReadObject();
// Well, this is a problem
bedSpawnLocation = new Location(
getWorld(input.readUTF()),
input.readDouble(),
input.readDouble(),
input.readDouble()
);
}
private World getWorld(String name) {
try {
// Try to get the world at least
return Bukkit.getServer().getWorld(name);
} catch (Exception e) {
// Screw it
return null;
}
}
@Override
public Player getPlayer() {
try {
// Try to get the real player underneath
return Bukkit.getServer().getPlayerExact(name);
} catch (Exception e) {
return getProxyPlayer();
}
}
/**
* Retrieve a player object that implements OfflinePlayer by refering to this object.
* <p>
* All other methods cause an exception.
* @return Proxy object.
*/
public Player getProxyPlayer() {
// Remember to initialize the method filter
if (lookup.size() == 0) {
// Add all public methods
for (Method method : OfflinePlayer.class.getMethods()) {
lookup.put(method.getName(), method);
}
}
// MORE CGLIB magic!
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
ex.setSuperclass(Player.class);
ex.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// There's no overloaded methods, so we don't care
Method offlineMethod = lookup.get(method.getName());
// Ignore all other methods
if (offlineMethod == null) {
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players.");
}
// Invoke our on method
return offlineMethod.invoke(SerializedOfflinePlayer.this, args);
}
});
return (Player) ex.create();
}
}
@@ -1,111 +0,0 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.netty;
import java.util.Map;
import java.util.Map.Entry;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.collect.Maps;
/**
* @author dmulloy2
*/
public class NettyProtocolRegistry extends ProtocolRegistry {
public NettyProtocolRegistry() {
super();
}
@Override
protected synchronized void initialize() {
ProtocolLogger.debug("Initializing the Netty protocol registry"); // Debug for issue #202
Object[] protocols = enumProtocol.getEnumConstants();
// ID to Packet class maps
Map<Object, Map<Integer, Class<?>>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Integer, Class<?>>> clientMaps = Maps.newLinkedHashMap();
Register result = new Register();
StructureModifier<Object> modifier = null;
// Iterate through the protocols
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Object, Map<Integer, Class<?>>>> maps = modifier.withTarget(protocol).withType(Map.class);
for (Entry<Object, Map<Integer, Class<?>>> entry : maps.read(0).entrySet()) {
String direction = entry.getKey().toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, entry.getValue());
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, entry.getValue());
}
}
}
// Maps we have to occasionally check have changed
for (Map<Integer, Class<?>> map : serverMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Map<Integer, Class<?>> map : clientMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (int i = 0; i < protocols.length; i++) {
Object protocol = protocols[i];
Enum<?> enumProtocol = (Enum<?>) protocol;
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
if (serverMaps.containsKey(protocol))
associatePackets(result, serverMaps.get(protocol), equivalent, Sender.SERVER);
if (clientMaps.containsKey(protocol))
associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
@Override
protected void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
for (Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromCurrent(protocol, sender, entry.getKey(), entry.getValue());
try {
register.typeToClass.put(type, entry.getValue());
if (sender == Sender.SERVER)
register.serverPackets.add(type);
if (sender == Sender.CLIENT)
register.clientPackets.add(type);
} catch (IllegalArgumentException ex) {
// Sometimes this happens with fake packets, just ignore it
}
}
}
}
@@ -1,173 +0,0 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.reflect.cloning;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.MinecraftKey;
import com.comphenix.protocol.wrappers.WrappedBlockData;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedServerPing;
import com.google.common.collect.Maps;
/**
* Represents an object that can clone a specific list of Bukkit- and Minecraft-related objects.
*
* @author Kristian
*/
public class BukkitCloner implements Cloner {
// List of classes we support
private final Map<Integer, Class<?>> clonableClasses = Maps.newConcurrentMap();
public BukkitCloner() {
addClass(0, MinecraftReflection.getItemStackClass());
addClass(1, MinecraftReflection.getDataWatcherClass());
// Try to add position classes
try {
addClass(2, MinecraftReflection.getBlockPositionClass());
} catch (Throwable ex) {
}
try {
addClass(3, MinecraftReflection.getChunkPositionClass());
} catch (Throwable ex) {
}
if (MinecraftReflection.isUsingNetty()) {
addClass(4, MinecraftReflection.getServerPingClass());
}
if (MinecraftReflection.watcherObjectExists()) {
addClass(5, MinecraftReflection.getDataWatcherSerializerClass());
addClass(6, MinecraftReflection.getMinecraftKeyClass());
}
try {
addClass(7, MinecraftReflection.getIBlockDataClass());
} catch (Throwable ex) {
}
try {
addClass(8, MinecraftReflection.getNonNullListClass());
} catch (Throwable ex) {
}
}
private void addClass(int id, Class<?> clazz) {
if (clazz != null)
clonableClasses.put(id, clazz);
}
private int findMatchingClass(Class<?> type) {
// See if is a subclass of any of our supported superclasses
for (Entry<Integer, Class<?>> entry : clonableClasses.entrySet()) {
if (entry.getValue().isAssignableFrom(type)) {
return entry.getKey();
}
}
return -1;
}
@Override
public boolean canClone(Object source) {
if (source == null)
return false;
return findMatchingClass(source.getClass()) >= 0;
}
@Override
public Object clone(Object source) {
if (source == null)
throw new IllegalArgumentException("source cannot be NULL.");
// Convert to a wrapper
switch (findMatchingClass(source.getClass())) {
case 0:
return MinecraftReflection.getMinecraftItemStack(MinecraftReflection.getBukkitItemStack(source).clone());
case 1:
EquivalentConverter<WrappedDataWatcher> dataConverter = BukkitConverters.getDataWatcherConverter();
return dataConverter.getGeneric(clonableClasses.get(1), dataConverter.getSpecific(source).deepClone());
case 2:
EquivalentConverter<BlockPosition> blockConverter = BlockPosition.getConverter();
return blockConverter.getGeneric(clonableClasses.get(2), blockConverter.getSpecific(source));
case 3:
EquivalentConverter<ChunkPosition> chunkConverter = ChunkPosition.getConverter();
return chunkConverter.getGeneric(clonableClasses.get(3), chunkConverter.getSpecific(source));
case 4:
EquivalentConverter<WrappedServerPing> serverConverter = BukkitConverters.getWrappedServerPingConverter();
return serverConverter.getGeneric(clonableClasses.get(4), serverConverter.getSpecific(source).deepClone());
case 5:
return source;
case 6:
EquivalentConverter<MinecraftKey> keyConverter = MinecraftKey.getConverter();
return keyConverter.getGeneric(clonableClasses.get(6), keyConverter.getSpecific(source));
case 7:
EquivalentConverter<WrappedBlockData> blockDataConverter = BukkitConverters.getWrappedBlockDataConverter();
return blockDataConverter.getGeneric(clonableClasses.get(7), blockDataConverter.getSpecific(source).deepClone());
case 8:
return nonNullListCloner().clone(source);
default:
throw new IllegalArgumentException("Cannot clone objects of type " + source.getClass());
}
}
private static Constructor<?> nonNullList = null;
private static final Cloner nonNullListCloner() {
return new Cloner() {
@Override
public boolean canClone(Object source) {
return MinecraftReflection.is(MinecraftReflection.getNonNullListClass(), source);
}
@Override
public Object clone(Object source) {
StructureModifier<Object> modifier = new StructureModifier<>(source.getClass(), true).withTarget(source);
List<?> list = (List<?>) modifier.read(0);
Object empty = modifier.read(1);
if (nonNullList == null) {
try {
nonNullList = source.getClass().getDeclaredConstructor(List.class, Object.class);
nonNullList.setAccessible(true);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Could not find NonNullList constructor", ex);
}
}
try {
return nonNullList.newInstance(new ArrayList<>(list), empty);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Could not create new NonNullList", ex);
}
}
};
}
}
@@ -1,57 +0,0 @@
package com.comphenix.protocol.reflect.compiler;
import net.sf.cglib.asm.AnnotationVisitor;
import net.sf.cglib.asm.Attribute;
import net.sf.cglib.asm.ClassVisitor;
import net.sf.cglib.asm.FieldVisitor;
import net.sf.cglib.asm.MethodVisitor;
public abstract class EmptyClassVisitor implements ClassVisitor {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
// NOP
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
// NOP
return null;
}
@Override
public void visitAttribute(Attribute attr) {
// NOP
}
@Override
public void visitEnd() {
// NOP
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
// NOP
return null;
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
// NOP
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
// NOP
return null;
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
// NOP
}
@Override
public void visitSource(String source, String debug) {
// NOP
}
}
@@ -1,132 +0,0 @@
package com.comphenix.protocol.reflect.compiler;
import net.sf.cglib.asm.AnnotationVisitor;
import net.sf.cglib.asm.Attribute;
import net.sf.cglib.asm.Label;
import net.sf.cglib.asm.MethodVisitor;
public class EmptyMethodVisitor implements MethodVisitor {
@Override
public AnnotationVisitor visitAnnotationDefault() {
// NOP
return null;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
// NOP
return null;
}
@Override
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
// NOP
return null;
}
@Override
public void visitAttribute(Attribute attr) {
// NOP
}
@Override
public void visitCode() {
// NOP
}
@Override
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
// NOP
}
@Override
public void visitInsn(int opcode) {
// NOP
}
@Override
public void visitIntInsn(int opcode, int operand) {
// NOP
}
@Override
public void visitVarInsn(int opcode, int var) {
// NOP
}
@Override
public void visitTypeInsn(int opcode, String type) {
// NOP
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
// NOP
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
// NOP
}
@Override
public void visitJumpInsn(int opcode, Label label) {
// NOP
}
@Override
public void visitLabel(Label label) {
// NOP
}
@Override
public void visitLdcInsn(Object cst) {
// NOP
}
@Override
public void visitIincInsn(int var, int increment) {
// NOP
}
@Override
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
// NOP
}
@Override
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
// NOP
}
@Override
public void visitMultiANewArrayInsn(String desc, int dims) {
// NOP
}
@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
// NOP
}
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start,
Label end, int index) {
// NOP
}
@Override
public void visitLineNumber(int line, Label start) {
// NOP
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// NOP
}
@Override
public void visitEnd() {
// NOP
}
}
@@ -1,44 +0,0 @@
package com.comphenix.protocol.utility;
import net.sf.cglib.proxy.Enhancer;
/**
* Represents a shared enchancer factory.
* @author Kristian
*/
public class EnhancerFactory {
private static EnhancerFactory INSTANCE = new EnhancerFactory();
// The current class loader
private ClassLoader loader = EnhancerFactory.class.getClassLoader();
public static EnhancerFactory getInstance() {
return INSTANCE;
}
/**
* Create a new CGLib enhancer.
* @return The new enhancer.
*/
public Enhancer createEnhancer() {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(loader);
return enhancer;
}
/**
* Set the current class loader to use when constructing enhancers.
* @param loader - the class loader
*/
public void setClassLoader(ClassLoader loader) {
this.loader = loader;
}
/**
* Get the current class loader we are using.
* @return The current class loader.
*/
public ClassLoader getClassLoader() {
return loader;
}
}
@@ -1,92 +0,0 @@
package com.comphenix.protocol.utility;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
/**
* Allows us to stay backwards compatible with older versions of Bukkit.
*
* @author Kristian
*/
public class WrappedScheduler {
/**
* Represents a backwards compatible Bukkit task.
*/
public static interface TaskWrapper {
/**
* Cancel the current task.
*/
public void cancel();
}
/**
* Schedule a given task for a single asynchronous execution.
* @param plugin - the owner plugin.
* @param runnable - the task to run.
* @param firstDelay - the amount of time to wait until executing the task.
* @return A cancel token.
*/
public static TaskWrapper runAsynchronouslyOnce(final Plugin plugin, Runnable runnable, long firstDelay) {
return runAsynchronouslyRepeat(plugin, plugin.getServer().getScheduler(), runnable, firstDelay, -1L);
}
/**
* Schedule a given task for multiple asynchronous executions.
* @param plugin - the owner plugin.
* @param runnable - the task to run.
* @param firstDelay - the amount of time to wait until executing the task for the first time.
* @param repeatDelay - the amount of time inbetween each execution. If less than zero, the task is only executed once.
* @return A cancel token.
*/
public static TaskWrapper runAsynchronouslyRepeat(final Plugin plugin, Runnable runnable, long firstDelay, long repeatDelay) {
return runAsynchronouslyRepeat(plugin, plugin.getServer().getScheduler(), runnable, firstDelay, repeatDelay);
}
/**
* Schedule a given task for asynchronous execution.
* @param plugin - the owner plugin.
* @param scheduler - the current Bukkit scheduler.
* @param runnable - the task to run.
* @param firstDelay - the amount of time to wait until executing the task for the first time.
* @param repeatDelay - the amount of time inbetween each execution. If less than zero, the task is only executed once.
* @return A cancel token.
*/
public static TaskWrapper runAsynchronouslyRepeat(final Plugin plugin, final BukkitScheduler scheduler, Runnable runnable, long firstDelay, long repeatDelay) {
try {
@SuppressWarnings("deprecation")
final int taskID = scheduler.scheduleAsyncRepeatingTask(plugin, runnable, firstDelay, repeatDelay);
// Return the cancellable object
return new TaskWrapper() {
@Override
public void cancel() {
scheduler.cancelTask(taskID);
}
};
} catch (NoSuchMethodError e) {
return tryUpdatedVersion(plugin, scheduler, runnable, firstDelay, repeatDelay);
}
}
/**
* Attempt to do the same with the updated scheduling method.
* @param plugin - the owner plugin.
* @param scheduler - the current Bukkit scheduler.
* @param runnable - the task to run.
* @param firstDelay - the amount of time to wait until executing the task for the first time.
* @param repeatDelay - the amount of time inbetween each execution. If less than zero, the task is only executed once.
* @return A cancel token.
*/
private static TaskWrapper tryUpdatedVersion(final Plugin plugin, final BukkitScheduler scheduler, Runnable runnable, long firstDelay, long repeatDelay) {
final BukkitTask task = scheduler.runTaskTimerAsynchronously(plugin, runnable, firstDelay, repeatDelay);
return new TaskWrapper() {
@Override
public void cancel() {
task.cancel();
}
};
}
}
File diff suppressed because it is too large. Load diff
@@ -1,183 +0,0 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.wrappers;
import java.lang.reflect.Modifier;
import org.bukkit.Material;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Represents a wrapper around IBlockData.
*
* @author dmulloy2
*/
public class WrappedBlockData extends AbstractWrapper {
private static final Class<?> MAGIC_NUMBERS = MinecraftReflection.getCraftBukkitClass("util.CraftMagicNumbers");
private static final Class<?> IBLOCK_DATA = MinecraftReflection.getIBlockDataClass();
private static final Class<?> BLOCK = MinecraftReflection.getBlockClass();
private static MethodAccessor FROM_LEGACY_DATA = null;
private static MethodAccessor TO_LEGACY_DATA = null;
private static MethodAccessor GET_NMS_BLOCK = null;
private static MethodAccessor GET_BLOCK = null;
static {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(BLOCK);
FuzzyMethodContract contract = FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.parameterExactArray(int.class)
.returnTypeExact(IBLOCK_DATA)
.build();
FROM_LEGACY_DATA = Accessors.getMethodAccessor(fuzzy.getMethod(contract));
contract = FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.parameterExactArray(IBLOCK_DATA)
.returnTypeExact(int.class)
.build();
TO_LEGACY_DATA = Accessors.getMethodAccessor(fuzzy.getMethod(contract, "toLegacyData"));
fuzzy = FuzzyReflection.fromClass(MAGIC_NUMBERS);
GET_NMS_BLOCK = Accessors.getMethodAccessor(fuzzy.getMethodByParameters("getBlock", BLOCK,
new Class<?>[] { Material.class }));
fuzzy = FuzzyReflection.fromClass(IBLOCK_DATA);
GET_BLOCK = Accessors.getMethodAccessor(fuzzy.getMethodByParameters("getBlock", BLOCK,
new Class<?>[0]));
}
public WrappedBlockData(Object handle) {
super(IBLOCK_DATA);
setHandle(handle);
}
/**
* Retrieves the type of this BlockData.
* @return The type of this BlockData.
*/
public Material getType() {
Object block = GET_BLOCK.invoke(handle);
return BukkitConverters.getBlockConverter().getSpecific(block);
}
/**
* Retrieves the type id of this BlockData.
* @return The type id of this BlockData.
* @deprecated ID's are deprecated
*/
@Deprecated
public int getTypeId() {
Object block = GET_BLOCK.invoke(handle);
return BukkitConverters.getBlockIDConverter().getSpecific(block);
}
/**
* Retrieves the data of this BlockData.
* @return The data of this BlockData.
*/
public int getData() {
Object block = GET_BLOCK.invoke(handle);
return (Integer) TO_LEGACY_DATA.invoke(block, handle);
}
/**
* Sets the type of this BlockData.
* @param type New type
*/
public void setType(Material type) {
setTypeAndData(type, 0);
}
/**
* Sets the data of this BlockData.
* @param data New data
*/
public void setData(int data) {
setTypeAndData(getType(), data);
}
/**
* Sets the type and data of this BlockData.
* @param type New type
* @param data New data
*/
public void setTypeAndData(Material type, int data) {
Object nmsBlock = GET_NMS_BLOCK.invoke(null, type);
Object blockData = FROM_LEGACY_DATA.invoke(nmsBlock, data);
setHandle(blockData);
}
/**
* Creates a new BlockData instance with the given type and no data.
* @param type Block type
* @return New BlockData
*/
public static WrappedBlockData createData(Material type) {
return createData(type, 0);
}
/**
* Creates a new BlockData instance with the given type and data.
* @param type Block type
* @param data Block data
* @return New BlockData
*/
public static WrappedBlockData createData(Material type, int data) {
Object nmsBlock = GET_NMS_BLOCK.invoke(null, type);
Object blockData = FROM_LEGACY_DATA.invoke(nmsBlock, data);
return new WrappedBlockData(blockData);
}
/**
* Retrieve a deep copy of the current wrapper object.
* @return The cloned object.
*/
public WrappedBlockData deepClone() {
return WrappedBlockData.createData(getType(), getData());
}
@Override
public String toString() {
return "WrappedBlockData[handle=" + handle + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getType().hashCode();
result = prime * result + getData();
return result;
}
@Override
public boolean equals(Object o) {
if (o instanceof WrappedBlockData) {
WrappedBlockData that = (WrappedBlockData) o;
return this.getType() == that.getType() && getData() == that.getData();
}
return false;
}
}
-290
View File
@@ -1,290 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name>
<version>4.2.1</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>http://www.spigotmc.org/resources/protocollib.1997/</url>
<packaging>jar</packaging>
<parent>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib-Parent</artifactId>
<version>v4-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.number></project.build.number>
<project.fullVersion>${project.version}</project.fullVersion>
</properties>
<build>
<defaultGoal>clean install</defaultGoal>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</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>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifestEntries>
<Main-Class>com.comphenix.protocol.Application</Main-Class>
<Implementation-Title>ProtocolLib</Implementation-Title>
<Implementation-Version>${project.fullVersion}</Implementation-Version>
<Implementation-Vendor>dmulloy2</Implementation-Vendor>
</manifestEntries>
</archive>
<finalName>${project.name}</finalName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.10</version>
<configuration>
<systemProperties>
<property>
<name>projectVersion</name>
<value>${project.version}</value>
</property>
</systemProperties>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>jenkins</id>
<activation>
<property>
<name>env.BUILD_NUMBER</name>
</property>
</activation>
<properties>
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
<project.fullVersion>${project.version}${project.build.number}</project.fullVersion>
</properties>
</profile>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<properties>
<project.fullVersion>${project.version}</project.fullVersion>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- TODO: Look into signing releases
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
-->
</plugins>
</build>
</profile>
</profiles>
<scm>
<connection>scm:git:git://github.com/dmulloy2/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:dmulloy2/ProtocolLib.git</developerConnection>
<url>https://github.com/dmulloy2/ProtocolLib</url>
</scm>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dmulloy2</id>
<name>Dan Mulloy</name>
<url>http://dmulloy2.net/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
</roles>
</developer>
<developer>
<id>aadnk</id>
<name>Kristian S. Stangeland</name>
<email>kr_stang@hotmail.com</email>
<url>http://comphenix.net/</url>
<roles>
<role>former author</role>
</roles>
<timezone>1</timezone>
</developer>
</developers>
<dependencies>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib-API</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Included with Minecraft
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.23.Final</version>
</dependency>
-->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.11.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.11.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.comphenix.executors</groupId>
<artifactId>BukkitExecutors</artifactId>
<version>1.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -1,170 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.lang.reflect.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]);
}
}
@@ -1,524 +0,0 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* A protocol manager that delays all packet listener registrations and unregistrations until
* an underlying protocol manager can be constructed.
*
* @author Kristian
*/
public class DelayedPacketManager implements InternalManager {
// Registering packet IDs that are not supported
public static final ReportType REPORT_CANNOT_SEND_QUEUED_PACKET = new ReportType("Cannot send queued packet %s.");
public static final ReportType REPORT_CANNOT_SEND_QUEUED_WIRE_PACKET = new ReportType("Cannot send queued wire packet %s.");
public static final ReportType REPORT_CANNOT_REGISTER_QUEUED_LISTENER = new ReportType("Cannot register queued listener %s.");
private volatile InternalManager delegate;
// Queued actions
private final List<Runnable> queuedActions = Collections.synchronizedList(Lists.<Runnable>newArrayList());
private final List<PacketListener> queuedListeners = Collections.synchronizedList(Lists.<PacketListener>newArrayList());
private AsynchronousManager asyncManager;
private ErrorReporter reporter;
// The current hook
private PlayerInjectHooks hook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// If we have been closed
private boolean closed;
private boolean debug;
// Queued registration
private PluginManager queuedManager;
private Plugin queuedPlugin;
private MinecraftVersion version;
public DelayedPacketManager(@Nonnull ErrorReporter reporter, @Nonnull MinecraftVersion version) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL.");
Preconditions.checkNotNull(version, "version cannot be NULL.");
this.reporter = reporter;
this.version = version;
}
/**
* Retrieve the underlying protocol manager.
* @return The underlying manager.
*/
public InternalManager getDelegate() {
return delegate;
}
@Override
public int getProtocolVersion(Player player) {
if (delegate != null)
return delegate.getProtocolVersion(player);
else
return Integer.MIN_VALUE;
}
@Override
public MinecraftVersion getMinecraftVersion() {
if (delegate != null)
return delegate.getMinecraftVersion();
else
return version;
}
/**
* Update the delegate to the underlying manager.
* <p>
* This will prompt this packet manager to immediately transmit and
* register all queued packets an listeners.
* @param delegate - delegate to the new manager.
*/
protected void setDelegate(InternalManager delegate) {
this.delegate = delegate;
if (delegate != null) {
// Update the hook if needed
if (!Objects.equal(delegate.getPlayerHook(), hook)) {
delegate.setPlayerHook(hook);
}
// Register events as well
if (queuedManager != null && queuedPlugin != null) {
delegate.registerEvents(queuedManager, queuedPlugin);
}
// And update the debug mode
delegate.setDebug(debug);
// Add any pending listeners
synchronized (queuedListeners) {
for (PacketListener listener : queuedListeners) {
try {
delegate.addPacketListener(listener);
} catch (IllegalArgumentException e) {
// Inform about this plugin error
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_QUEUED_LISTENER).
callerParam(delegate).messageParam(listener).error(e));
}
}
}
// Execute any delayed actions
synchronized (queuedActions) {
for (Runnable action : queuedActions) {
action.run();
}
}
// Don't keep this around anymore
queuedListeners.clear();
queuedActions.clear();
}
}
private Runnable queuedAddPacket(final ConnectionSide side, final Player player, final PacketContainer packet,
final NetworkMarker marker, final boolean filtered) {
return new Runnable() {
@Override
public void run() {
try {
// Attempt to send it now
switch (side) {
case CLIENT_SIDE:
delegate.recieveClientPacket(player, packet, marker, filtered);
break;
case SERVER_SIDE:
delegate.sendServerPacket(player, packet, marker, filtered);
break;
default:
throw new IllegalArgumentException("side cannot be " + side);
}
} catch (Exception e) {
// Inform about this plugin error
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_PACKET).
callerParam(delegate).messageParam(packet).error(e));
}
}
};
}
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
this.hook = playerHook;
}
@Override
public PlayerInjectHooks getPlayerHook() {
return hook;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet) throws InvocationTargetException {
sendServerPacket(receiver, packet, null, true);
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters) throws InvocationTargetException {
sendServerPacket(receiver, packet, null, filters);
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
if (delegate != null) {
delegate.sendServerPacket(receiver, packet, marker, filters);
} else {
queuedActions.add(queuedAddPacket(ConnectionSide.SERVER_SIDE, receiver, packet, marker, filters));
}
}
@Override
public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException {
WirePacket packet = new WirePacket(id, bytes);
sendWirePacket(receiver, packet);
}
@Override
public void sendWirePacket(final Player receiver, final WirePacket packet) throws InvocationTargetException {
if (delegate != null) {
delegate.sendWirePacket(receiver, packet);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
try {
delegate.sendWirePacket(receiver, packet);
} catch (Throwable ex) {
// Inform about this plugin error
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_WIRE_PACKET)
.callerParam(delegate)
.messageParam(packet)
.error(ex));
}
}
});
}
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, null, true);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, null, filters);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) throws IllegalAccessException, InvocationTargetException {
if (delegate != null) {
delegate.recieveClientPacket(sender, packet, marker, filters);
} else {
queuedActions.add(queuedAddPacket(ConnectionSide.CLIENT_SIDE, sender, packet, marker, filters));
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet, final Entity entity, final boolean includeTracker) {
if (delegate != null) {
delegate.broadcastServerPacket(packet, entity, includeTracker);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet, entity, includeTracker);
}
});
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet, final Location origin, final int maxObserverDistance) {
if (delegate != null) {
delegate.broadcastServerPacket(packet, origin, maxObserverDistance);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet, origin, maxObserverDistance);
}
});
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet) {
if (delegate != null) {
delegate.broadcastServerPacket(packet);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet);
}
});
}
}
@Override
public ImmutableSet<PacketListener> getPacketListeners() {
if (delegate != null)
return delegate.getPacketListeners();
else
return ImmutableSet.copyOf(queuedListeners);
}
@Override
public void addPacketListener(PacketListener listener) {
if (delegate != null)
delegate.addPacketListener(listener);
else
queuedListeners.add(listener);
}
@Override
public void removePacketListener(PacketListener listener) {
if (delegate != null)
delegate.removePacketListener(listener);
else
queuedListeners.remove(listener);
}
@Override
public void removePacketListeners(Plugin plugin) {
if (delegate != null) {
delegate.removePacketListeners(plugin);
} else {
for (Iterator<PacketListener> it = queuedListeners.iterator(); it.hasNext(); ) {
// Remove listeners of the same plugin
if (Objects.equal(it.next().getPlugin(), plugin)) {
it.remove();
}
}
}
}
@Override
@Deprecated
public PacketContainer createPacket(int id) {
if (delegate != null)
return delegate.createPacket(id);
return createPacket(id, true);
}
@Override
@Deprecated
public PacketContainer createPacket(int id, boolean forceDefaults) {
if (delegate != null) {
return delegate.createPacket(id);
} else {
// Fallback implementation
PacketContainer packet = new PacketContainer(id);
// Use any default values if possible
if (forceDefaults) {
try {
packet.getModifier().writeDefaults();
} catch (FieldAccessException e) {
throw new RuntimeException("Security exception.", e);
}
}
return packet;
}
}
@SuppressWarnings("deprecation")
@Override
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
if (delegate != null)
return delegate.createPacketConstructor(id, arguments);
else
return PacketConstructor.DEFAULT.withPacket(id, arguments);
}
@Override
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments) {
if (delegate != null)
return delegate.createPacketConstructor(type, arguments);
else
return PacketConstructor.DEFAULT.withPacket(type, arguments);
}
@Override
@Deprecated
public Set<Integer> getSendingFilters() {
if (delegate != null) {
return delegate.getSendingFilters();
} else {
// Linear scan is fast enough here
Set<Integer> sending = Sets.newHashSet();
for (PacketListener listener : queuedListeners) {
sending.addAll(listener.getSendingWhitelist().getWhitelist());
}
return sending;
}
}
@Override
@Deprecated
public Set<Integer> getReceivingFilters() {
if (delegate != null) {
return delegate.getReceivingFilters();
} else {
Set<Integer> recieving = Sets.newHashSet();
for (PacketListener listener : queuedListeners) {
recieving.addAll(listener.getReceivingWhitelist().getWhitelist());
}
return recieving;
}
}
@Override
public PacketContainer createPacket(PacketType type) {
return createPacket(type.getLegacyId());
}
@Override
public PacketContainer createPacket(PacketType type, boolean forceDefaults) {
return createPacket(type.getLegacyId(), forceDefaults);
}
@Override
public Set<PacketType> getSendingFilterTypes() {
return PacketRegistry.toPacketTypes(getSendingFilters(), Sender.SERVER);
}
@Override
public Set<PacketType> getReceivingFilterTypes() {
return PacketRegistry.toPacketTypes(getReceivingFilters(), Sender.CLIENT);
}
@Override
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
if (delegate != null)
delegate.updateEntity(entity, observers);
else
EntityUtilities.updateEntity(entity, observers);
}
@Override
public Entity getEntityFromID(World container, int id) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityFromID(container, id);
else
return EntityUtilities.getEntityFromID(container, id);
}
@Override
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityTrackers(entity);
else
return EntityUtilities.getEntityTrackers(entity);
}
@Override
public boolean isClosed() {
return closed || (delegate != null && delegate.isClosed());
}
@Override
public AsynchronousManager getAsynchronousManager() {
if (delegate != null)
return delegate.getAsynchronousManager();
else
return asyncManager;
}
@Override
public boolean isDebug() {
return debug;
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;
if (delegate != null) {
delegate.setDebug(debug);
}
}
/**
* Update the asynchronous manager. This must be set.
* @param asyncManager - the asynchronous manager.
*/
public void setAsynchronousManager(AsynchronousManager asyncManager) {
this.asyncManager = asyncManager;
}
@Override
public void registerEvents(PluginManager manager, Plugin plugin) {
if (delegate != null) {
delegate.registerEvents(manager, plugin);
} else {
queuedManager = manager;
queuedPlugin = plugin;
}
}
@Override
public void close() {
if (delegate != null)
delegate.close();
closed = true;
}
@Override
public void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) {
for (PacketType type : whitelist.getTypes()) {
if (type == null) {
throw new IllegalArgumentException(String.format("Packet type in in listener %s was NULL.",
PacketAdapter.getPluginName(listener))
);
}
}
}
}
@@ -1,256 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.Validate;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
import com.google.common.collect.Lists;
/**
* Used to perform certain operations on entities.
*
* @author Kristian
*/
class EntityUtilities {
private static Field entityTrackerField;
private static Field trackedEntitiesField;
private static Field trackedPlayersField;
private static Field trackerField;
private static Method scanPlayersMethod;
/*
public static void updateEntity2(Entity entity, List<Player> observers) {
EntityTrackerEntry entry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
List<EntityPlayer> nmsPlayers = getNmsPlayers(observers);
entry.trackedPlayers.removeAll(nmsPlayers);
entry.scanPlayers(nmsPlayers);
}
*/
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
if (entity == null || !entity.isValid()) {
return;
}
try {
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
if (trackerEntry == null) {
throw new IllegalArgumentException("Cannot find entity trackers for " + entity + ".");
}
if (trackedPlayersField == null) {
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
}
// Phew, finally there.
Collection<?> trackedPlayers = getTrackedPlayers(trackedPlayersField, trackerEntry);
List<Object> nmsPlayers = unwrapBukkit(observers);
trackedPlayers.removeAll(nmsPlayers);
// We have to rely on a NAME once again. Damn it.
// TODO: Make sure this stays up to date with version changes - 1.8 - 1.10
if (scanPlayersMethod == null) {
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
}
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
} catch (IllegalArgumentException e) {
throw e;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occurred in Minecraft.", e);
} catch (SecurityException e) {
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
} catch (NoSuchMethodException e) {
throw new FieldAccessException("Cannot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
}
}
/**
* 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 static List<Player> getEntityTrackers(Entity entity) {
if (entity == null || !entity.isValid()) {
return new ArrayList<>();
}
try {
List<Player> result = new ArrayList<Player>();
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
if (trackerEntry == null) {
throw new IllegalArgumentException("Cannot find entity trackers for " + entity + ".");
}
if (trackedPlayersField == null) {
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
}
Collection<?> trackedPlayers = getTrackedPlayers(trackedPlayersField, trackerEntry);
// Wrap every player - we also ensure that the underlying tracker list is immutable
for (Object tracker : trackedPlayers) {
if (MinecraftReflection.isMinecraftPlayer(tracker)) {
result.add((Player) MinecraftReflection.getBukkitEntity(tracker));
}
}
return result;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Security limitation prevented access to the list of tracked players.", e);
}
}
// Damn you, Paper
private static Collection<?> getTrackedPlayers(Field field, Object entry) throws IllegalAccessException {
Validate.notNull(field, "Cannot find 'trackedPlayers' field.");
Validate.notNull(entry, "entry cannot be null!");
Object value = FieldUtils.readField(field, entry, false);
if (value instanceof Collection) {
return (Collection<?>) value;
} else if (value instanceof Map) {
return ((Map<?, ?>) value).keySet();
} else {
// Please. No more changes.
throw new IllegalStateException("trackedPlayers field was an unknown type: expected Collection or Map, but got " + value.getClass());
}
}
/*
private static EntityTrackerEntry getEntityTrackerEntry2(World world, int entityID) {
WorldServer worldServer = ((CraftWorld) world).getHandle();
EntityTracker tracker = worldServer.tracker;
return tracker.trackedEntities.get(entityID);
}
*/
private static Object getEntityTrackerEntry(World world, int entityID) throws FieldAccessException, IllegalArgumentException {
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
Object worldServer = unwrapper.unwrapItem(world);
if (entityTrackerField == null)
entityTrackerField = FuzzyReflection.fromObject(worldServer).
getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass());
// Get the tracker
Object tracker = null;
try {
tracker = FieldUtils.readField(entityTrackerField, worldServer, false);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'tracker' field due to security limitations.", e);
}
// Looking for an IntHashMap in the tracker entry
if (trackedEntitiesField == null) {
trackedEntitiesField = FuzzyReflection.fromObject(tracker, false)
.getFieldByType("trackedEntities", MinecraftReflection.getIntHashMapClass());
}
// Read the map
Object trackedEntities = null;
try {
trackedEntities = FieldUtils.readField(trackedEntitiesField, tracker, false);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'trackedEntities' field due to security limitations.", e);
}
return WrappedIntHashMap.fromHandle(trackedEntities).get(entityID);
}
/**
* Retrieve entity from a ID, even it it's newly created.
* @return The associated entity.
* @throws FieldAccessException Reflection error.
*/
public static Entity getEntityFromID(World world, int entityID) throws FieldAccessException {
try {
Object trackerEntry = getEntityTrackerEntry(world, entityID);
Object tracker = null;
// Handle NULL cases
if (trackerEntry != null) {
if (trackerField == null) {
try {
Class<?> entryClass = MinecraftReflection.getMinecraftClass("EntityTrackerEntry");
trackerField = entryClass.getDeclaredField("tracker");
} catch (NoSuchFieldException e) {
// Assume it's the first entity field then
trackerField = FuzzyReflection.fromObject(trackerEntry, true)
.getFieldByType("tracker", MinecraftReflection.getEntityClass());
}
}
tracker = FieldUtils.readField(trackerField, trackerEntry, true);
}
// If the tracker is NULL, we'll just assume this entity doesn't exist
if (tracker != null)
return (Entity) MinecraftReflection.getBukkitEntity(tracker);
else
return null;
} catch (Exception e) {
throw new FieldAccessException("Cannot find entity from ID " + entityID + ".", e);
}
}
private static List<Object> unwrapBukkit(List<Player> players) {
List<Object> output = Lists.newArrayList();
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
// Get the NMS equivalent
for (Player player : players) {
Object result = unwrapper.unwrapItem(player);
if (result != null)
output.add(result);
else
throw new IllegalArgumentException("Cannot unwrap item " + player);
}
return output;
}
}
@@ -1,92 +0,0 @@
package com.comphenix.protocol.injector;
import org.bukkit.Bukkit;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
/**
* Packets that are known to be transmitted during login.
* <p>
* This may be dynamically extended later.
* @author Kristian
*/
class LoginPackets {
private IntegerSet clientSide = new IntegerSet(Packets.PACKET_COUNT);
private IntegerSet serverSide = new IntegerSet(Packets.PACKET_COUNT);
@SuppressWarnings("deprecation")
public LoginPackets(MinecraftVersion version) {
// Ordinary login
clientSide.add(Packets.Client.HANDSHAKE);
serverSide.add(Packets.Server.KEY_REQUEST);
clientSide.add(Packets.Client.KEY_RESPONSE);
serverSide.add(Packets.Server.KEY_RESPONSE);
clientSide.add(Packets.Client.CLIENT_COMMAND);
serverSide.add(Packets.Server.LOGIN);
// List ping
clientSide.add(Packets.Client.GET_INFO);
// In 1.6.2, Minecraft started sending CUSTOM_PAYLOAD in the server list protocol
// MCPC+/Cauldron contains Forge, which uses CUSTOM_PAYLOAD during login
if (version.isAtLeast(MinecraftVersion.HORSE_UPDATE) || isCauldronOrMCPC()) {
clientSide.add(Packets.Client.CUSTOM_PAYLOAD);
}
if (isCauldronOrMCPC()) {
serverSide.add(Packets.Server.CUSTOM_PAYLOAD);
}
serverSide.add(Packets.Server.KICK_DISCONNECT);
}
/**
* Determine if we are running MCPC or Cauldron.
* @return TRUE if we are, FALSE otherwise.
*/
private static boolean isCauldronOrMCPC() {
String version = Bukkit.getServer().getVersion();
return version.contains("MCPC") || version.contains("Cauldron");
}
/**
* Determine if a packet may be sent during login from a given direction.
* @param packetId - the ID of the packet.
* @param side - the direction.
* @return TRUE if it may, FALSE otherwise.
*/
@Deprecated
public boolean isLoginPacket(int packetId, ConnectionSide side) {
switch (side) {
case CLIENT_SIDE:
return clientSide.contains(packetId);
case SERVER_SIDE:
return serverSide.contains(packetId);
case BOTH:
return clientSide.contains(packetId) ||
serverSide.contains(packetId);
default:
throw new IllegalArgumentException("Unknown connection side: " + side);
}
}
/**
* Determine if a given packet may be sent during login.
* @param type - the packet type.
* @return TRUE if it may, FALSE otherwise.
*/
public boolean isLoginPacket(PacketType type) {
if (!MinecraftReflection.isUsingNetty())
return isLoginPacket(type.getLegacyId(), type.getSender().toSide());
return PacketType.Login.Client.getInstance().hasMember(type) ||
PacketType.Login.Server.getInstance().hasMember(type) ||
PacketType.Status.Client.getInstance().hasMember(type) ||
PacketType.Status.Server.getInstance().hasMember(type);
}
}
@@ -1,64 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Represents an input stream that stores every read block of bytes in another output stream.
*
* @author Kristian
*/
class CaptureInputStream extends FilterInputStream {
protected OutputStream out;
public CaptureInputStream(InputStream in, OutputStream out) {
super(in);
this.out = out;
}
@Override
public int read() throws IOException {
int value = super.read();
// Write the byte
if (value >= 0)
out.write(value);
return value;
}
@Override
public void close() throws IOException {
super.close();
out.close();
}
@Override
public int read(byte[] b) throws IOException {
int count = super.read(b);
if (count > 0 ) {
out.write(b, 0, count);
}
return count;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int count = super.read(b, off, len);
if (count > 0 ) {
out.write(b, off, count);
}
return count;
}
/**
* Retrieve the output stream that receives all the read information.
* @return The output stream everything is copied to.
*/
public OutputStream getOutputStream() {
return out;
}
}
@@ -1,154 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.io.DataOutput;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;
/**
* Retrieve a packet instance that has its write method intercepted.
* @author Kristian
*/
public class InterceptWritePacket {
public static final ReportType REPORT_CANNOT_FIND_WRITE_PACKET_METHOD = new ReportType("Cannot find write packet method in %s.");
public static final ReportType REPORT_CANNOT_CONSTRUCT_WRITE_PROXY = new ReportType("Cannot construct write proxy packet %s.");
/**
* Matches the readPacketData(DataInputStream) method in Packet.
*/
private static FuzzyMethodContract WRITE_PACKET = FuzzyMethodContract.newBuilder().
returnTypeVoid().
parameterDerivedOf(DataOutput.class).
parameterCount(1).
build();
private CallbackFilter filter;
private boolean writePacketIntercepted;
private ConcurrentMap<Integer, Class<?>> proxyClasses = Maps.newConcurrentMap();
private ErrorReporter reporter;
private WritePacketModifier modifierWrite;
private WritePacketModifier modifierRest;
public InterceptWritePacket(ErrorReporter reporter) {
this.reporter = reporter;
// Initialize modifiers
this.modifierWrite = new WritePacketModifier(reporter, true);
this.modifierRest = new WritePacketModifier(reporter, false);
}
// TODO: PacketId should probably do something...
private Class<?> createProxyClass() {
// Construct the proxy object
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
// Attempt to share callback filter
if (filter == null) {
filter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Skip methods defined in Object
if (WRITE_PACKET.isMatch(MethodInfo.fromMethod(method), null)) {
writePacketIntercepted = true;
return 0;
} else {
return 1;
}
}
};
}
// Subclass the generic packet class
ex.setSuperclass(MinecraftReflection.getPacketClass());
ex.setCallbackFilter(filter);
ex.setUseCache(false);
ex.setCallbackTypes( new Class[] { WritePacketModifier.class, WritePacketModifier.class });
Class<?> proxyClass = ex.createClass();
// Register write modifiers too
Enhancer.registerStaticCallbacks(proxyClass, new Callback[] { modifierWrite, modifierRest });
if (proxyClass != null) {
// Check that we found the read method
if (!writePacketIntercepted) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_FIND_WRITE_PACKET_METHOD).
messageParam(MinecraftReflection.getPacketClass()));
}
}
return proxyClass;
}
@SuppressWarnings("deprecation")
private Class<?> getProxyClass(int packetId) {
Class<?> stored = proxyClasses.get(packetId);
// Concurrent pattern
if (stored == null) {
final Class<?> created = createProxyClass();
stored = proxyClasses.putIfAbsent(packetId, created);
// We won!
if (stored == null) {
stored = created;
PacketRegistry.getPacketToID().put(stored, packetId);
}
}
return stored;
}
/**
* Construct a new instance of the proxy object.
* @param proxyObject - Object to construct proxy of
* @param event - Packet event
* @param marker - Network marker
* @return New instance of the proxy, or null if we failed.
*/
@SuppressWarnings("deprecation")
public Object constructProxy(Object proxyObject, PacketEvent event, NetworkMarker marker) {
Class<?> proxyClass = null;
try {
proxyClass = getProxyClass(event.getPacketID());
Object generated = proxyClass.newInstance();
modifierWrite.register(generated, proxyObject, event, marker);
modifierRest.register(generated, proxyObject, event, marker);
return generated;
} catch (Exception e) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_CONSTRUCT_WRITE_PROXY).
messageParam(proxyClass));
return null;
}
}
/**
* Invoked when the write packet proxy class should be removed.
*/
@SuppressWarnings("deprecation")
public void cleanup() {
// Remove all proxy classes from the registry
for (Class<?> stored : proxyClasses.values()) {
PacketRegistry.getPacketToID().remove(stored);
}
}
}
@@ -1,66 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ForwardingMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
public class InverseMaps {
private InverseMaps() {
// Not constructable
}
public static <K, V> Multimap<K, V> inverseMultimap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
final MapContainer container = new MapContainer(map);
return new ForwardingMultimap<K, V>() {
// The cached multimap
private Multimap<K, V> inverseMultimap;
@Override
protected Multimap<K, V> delegate() {
if (container.hasChanged()) {
inverseMultimap = HashMultimap.create();
// Construct the inverse map
for (Map.Entry<V, K> entry : map.entrySet()) {
if (filter.apply(entry)) {
inverseMultimap.put(entry.getValue(), entry.getKey());
}
}
container.setChanged(false);
}
return inverseMultimap;
}
};
}
public static <K, V> Map<K, V> inverseMap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
final MapContainer container = new MapContainer(map);
return new ForwardingMap<K, V>() {
// The cached map
private Map<K, V> inverseMap;
@Override
protected Map<K, V> delegate() {
if (container.hasChanged()) {
inverseMap = Maps.newHashMap();
// Construct the inverse map
for (Map.Entry<V, K> entry : map.entrySet()) {
if (filter.apply(entry)) {
inverseMap.put(entry.getValue(), entry.getKey());
}
}
container.setChanged(false);
}
return inverseMap;
}
};
}
}
@@ -1,65 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.google.common.io.ByteSource;
import com.google.common.primitives.Bytes;
/**
* Represents a network marker for 1.6.4 and earlier.
* @author Kristian
*/
public class LegacyNetworkMarker extends NetworkMarker {
public LegacyNetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer, PacketType type) {
super(side, inputBuffer, type);
}
public LegacyNetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer, PacketType type) {
super(side, inputBuffer, type);
}
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
// This has already been done
return input;
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
return ByteBuffer.wrap(Bytes.concat(new byte[] { (byte) type.getLegacyId() }, buffer.array()));
}
@Override
protected DataInputStream addHeader(final DataInputStream input, final PacketType type) {
ByteSource header = new ByteSource() {
@Override
public InputStream openStream() throws IOException {
byte[] data = new byte[] { (byte) type.getLegacyId() };
return new ByteArrayInputStream(data);
}
};
ByteSource data = new ByteSource() {
@Override
public InputStream openStream() throws IOException {
return input;
}
};
// Combine them into a single stream
try {
return new DataInputStream(ByteSource.concat(header, data).openStream());
} catch (IOException e) {
throw new RuntimeException("Cannot add header.", e);
}
}
}
@@ -1,334 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
import net.sf.cglib.proxy.Factory;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.TroveWrapper;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
@SuppressWarnings("rawtypes")
class LegacyPacketRegistry {
private static final int MIN_SERVER_PACKETS = 5;
private static final int MIN_CLIENT_PACKETS = 5;
// Fuzzy reflection
private FuzzyReflection packetRegistry;
// The packet class to packet ID translator
private Map<Class, Integer> packetToID;
// Packet IDs to classes, grouped by whether or not they're vanilla or custom defined
private Multimap<Integer, Class> customIdToPacket;
private Map<Integer, Class> vanillaIdToPacket;
// Whether or not certain packets are sent by the client or the server
private ImmutableSet<Integer> serverPackets;
private ImmutableSet<Integer> clientPackets;
// The underlying sets
private Set<Integer> serverPacketsRef;
private Set<Integer> clientPacketsRef;
// New proxy values
private Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
// Vanilla packets
private Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
/**
* Initialize the registry.
*/
@SuppressWarnings({ "unchecked" })
public void initialize() {
if (packetToID == null) {
try {
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalArgumentException e) {
// Spigot 1.2.5 MCPC workaround
try {
packetToID = getSpigotWrapper();
} catch (Exception e2) {
// Very bad indeed
throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2);
}
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
}
// Create the inverse maps
customIdToPacket = InverseMaps.inverseMultimap(packetToID, new Predicate<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> entry) {
return !MinecraftReflection.isMinecraftClass(entry.getKey());
}
});
// And the vanilla pack - here we assume a unique ID to class mapping
vanillaIdToPacket = InverseMaps.inverseMap(packetToID, new Predicate<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> entry) {
return MinecraftReflection.isMinecraftClass(entry.getKey());
}
});
}
initializeSets();
}
@SuppressWarnings("unchecked")
private void initializeSets() throws FieldAccessException {
if (serverPacketsRef == null || clientPacketsRef == null) {
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
try {
if (sets.size() > 1) {
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
// Impossible
if (serverPacketsRef == null || clientPacketsRef == null)
throw new FieldAccessException("Packet sets are in an illegal state.");
// NEVER allow callers to modify the underlying sets
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
// Check sizes
if (serverPackets.size() < MIN_SERVER_PACKETS)
throw new InsufficientPacketsException("Insufficient server packets.", false, serverPackets.size());
if (clientPackets.size() < MIN_CLIENT_PACKETS)
throw new InsufficientPacketsException("Insufficient client packets.", true, clientPackets.size());
} else {
throw new FieldAccessException("Cannot retrieve packet client/server sets.");
}
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access field.", e);
}
} else {
// Copy over again if it has changed
if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size())
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
}
}
/**
* Retrieve the packet mapping.
* @return The packet map.
*/
public Map<Class, Integer> getPacketToID() {
// Initialize it, if we haven't already
if (packetToID == null) {
initialize();
}
return packetToID;
}
private Map<Class, Integer> getSpigotWrapper() throws IllegalAccessException {
// If it talks like a duck, etc.
// Perhaps it would be nice to have a proper duck typing library as well
FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
method(FuzzyMethodContract.newBuilder().
nameExact("size").returnTypeExact(int.class)).
method(FuzzyMethodContract.newBuilder().
nameExact("put").parameterCount(2)).
method(FuzzyMethodContract.newBuilder().
nameExact("get").parameterCount(1)).
build();
Field packetsField = getPacketRegistry().getField(
FuzzyFieldContract.newBuilder().typeMatches(mapLike).build());
Object troveMap = FieldUtils.readStaticField(packetsField, true);
// Fix incorrect no entry values
TroveWrapper.transformNoEntryValue(troveMap, new Function<Integer, Integer>() {
public Integer apply(Integer value) {
if (value >= 0 && value < 256) {
// Someone forgot to set the no entry value. Let's help them.
return -1;
}
return value;
}
});
// We'll assume this a Trove map
return TroveWrapper.getDecoratedMap(troveMap);
}
/**
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
* @return Reflected packet registry.
*/
private FuzzyReflection getPacketRegistry() {
if (packetRegistry == null)
packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true);
return packetRegistry;
}
/**
* Retrieve the injected proxy classes handlig each packet ID.
* @return Injected classes.
*/
public Map<Integer, Class> getOverwrittenPackets() {
return overwrittenPackets;
}
/**
* Retrieve the vanilla classes handling each packet ID.
* @return Vanilla classes.
*/
public Map<Integer, Class> getPreviousPackets() {
return previousValues;
}
/**
* Retrieve every known and supported server packet.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
public Set<Integer> getServerPackets() throws FieldAccessException {
initializeSets();
// Sanity check. This is impossible!
if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS)
throw new FieldAccessException("Server packet list is empty. Seems to be unsupported");
return serverPackets;
}
/**
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public Set<Integer> getClientPackets() throws FieldAccessException {
initializeSets();
// As above
if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS)
throw new FieldAccessException("Client packet list is empty. Seems to be unsupported");
return clientPackets;
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @return The associated class.
*/
public Class getPacketClassFromID(int packetID) {
return getPacketClassFromID(packetID, false);
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
public Class getPacketClassFromID(int packetID, boolean forceVanilla) {
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
Class<?> result = null;
// Optimized lookup
if (lookup.containsKey(packetID)) {
return removeEnhancer(lookup.get(packetID), forceVanilla);
}
// Refresh lookup tables
getPacketToID();
// See if we can look for non-vanilla classes
if (!forceVanilla) {
result = Iterables.getFirst(customIdToPacket.get(packetID), null);
}
if (result == null) {
result = vanillaIdToPacket.get(packetID);
}
// See if we got it
if (result != null)
return result;
else
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
}
/**
* Retrieve the packet ID of a given packet.
* @param packet - the type of packet to check.
* @return The ID of the given packet.
* @throws IllegalArgumentException If this is not a valid packet.
*/
public int getPacketID(Class<?> packet) {
if (packet == null)
throw new IllegalArgumentException("Packet type class cannot be NULL.");
if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
throw new IllegalArgumentException("Type must be a packet.");
// The registry contains both the overridden and original packets
return getPacketToID().get(packet);
}
/**
* Find the first superclass that is not a CBLib proxy object.
* @param clazz - the class whose hierachy we're going to search through.
* @param remove - whether or not to skip enhanced (proxy) classes.
* @return If remove is TRUE, the first superclass that is not a proxy.
*/
private static Class removeEnhancer(Class clazz, boolean remove) {
if (remove) {
// Get the underlying vanilla class
while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) {
clazz = clazz.getSuperclass();
}
}
return clazz;
}
/**
* Occurs when we were unable to retrieve all the packets in the registry.
* @author Kristian
*/
public static class InsufficientPacketsException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final boolean client;
private final int packetCount;
private InsufficientPacketsException(String message, boolean client, int packetCount) {
super(message);
this.client = client;
this.packetCount = packetCount;
}
public boolean isClient() {
return client;
}
public int getPacketCount() {
return packetCount;
}
}
}
@@ -1,96 +0,0 @@
package com.comphenix.protocol.injector.packet;
import javax.annotation.Nonnull;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.base.Preconditions;
/**
* A builder responsible for creating incoming packet injectors.
*
* @author Kristian
*/
public class PacketInjectorBuilder {
protected PacketInjectorBuilder() {
// No need to construct this
}
/**
* Retrieve a new packet injector builder.
* @return Injector builder.
*/
public static PacketInjectorBuilder newBuilder() {
return new PacketInjectorBuilder();
}
protected ListenerInvoker invoker;
protected ErrorReporter reporter;
protected PlayerInjectionHandler playerInjection;
/**
* The error reporter used by the created injector.
* @param reporter - new error reporter.
* @return This builder, for chaining.
*/
public PacketInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
this.reporter = reporter;
return this;
}
/**
* The packet stream invoker.
* @param invoker - the invoker.
* @return This builder, for chaining.
*/
public PacketInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
this.invoker = invoker;
return this;
}
/**
* Set the player injection.
* @param playerInjection - the injection.
* @return This builder, for chaining.
*/
@Nonnull
public PacketInjectorBuilder playerInjection(@Nonnull PlayerInjectionHandler playerInjection) {
Preconditions.checkNotNull(playerInjection, "playerInjection cannot be NULL");
this.playerInjection = playerInjection;
return this;
}
/**
* Called before an object is created with this builder.
*/
private void initializeDefaults() {
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
// Initialize with default values if we can
if (reporter == null)
reporter = ProtocolLibrary.getErrorReporter();
if (invoker == null)
invoker = (PacketFilterManager) manager;
if (playerInjection == null)
throw new IllegalStateException("Player injection parameter must be initialized.");
}
/**
* Create a packet injector using the provided fields or the default values.
* <p>
* Note that any non-null builder parameters must be set.
* @return The created injector.
* @throws FieldAccessException If anything goes wrong in terms of reflection.
*/
public PacketInjector buildInjector() throws FieldAccessException {
initializeDefaults();
return new ProxyPacketInjector(invoker, playerInjection, reporter);
}
}
@@ -1,372 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.packet;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
/**
* This class is responsible for adding or removing proxy objects that intercepts received packets.
*
* @author Kristian
*/
class ProxyPacketInjector implements PacketInjector {
public static final ReportType REPORT_CANNOT_FIND_READ_PACKET_METHOD = new ReportType("Cannot find read packet method for ID %s.");
public static final ReportType REPORT_UNKNOWN_ORIGIN_FOR_PACKET = new ReportType("Timeout: Unknown origin %s for packet %s. Are you using GamePhase.LOGIN?");
/**
* Represents a way to update the packet ID to class lookup table.
* @author Kristian
*/
private static interface PacketClassLookup {
public void setLookup(int packetID, Class<?> clazz);
}
private static class IntHashMapLookup implements PacketClassLookup {
private WrappedIntHashMap intHashMap;
public IntHashMapLookup() throws IllegalAccessException {
initialize();
}
@Override
public void setLookup(int packetID, Class<?> clazz) {
intHashMap.put(packetID, clazz);
}
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(MinecraftReflection.getPacketClass(), true).
getFieldByType("packetIdMap", MinecraftReflection.getIntHashMapClass());
try {
intHashMap = WrappedIntHashMap.fromHandle(
FieldUtils.readField(intHashMapField, (Object) null, true));
} catch (IllegalArgumentException e) {
throw new RuntimeException("Minecraft is incompatible.", e);
}
}
}
}
private static class ArrayLookup implements PacketClassLookup {
private Class<?>[] array;
public ArrayLookup() throws IllegalAccessException {
initialize();
}
@Override
public void setLookup(int packetID, Class<?> clazz) {
array[packetID] = clazz;
}
private void initialize() throws IllegalAccessException {
FuzzyReflection reflection = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass());
// Is there a Class array with 256 elements instead?
for (Field field : reflection.getFieldListByType(Class[].class)) {
Class<?>[] test = (Class<?>[]) FieldUtils.readField(field, (Object)null);
if (test.length == 256) {
array = test;
return;
}
}
throw new IllegalArgumentException(
"Unable to find an array with the type " + Class[].class +
" in " + MinecraftReflection.getPacketClass());
}
}
/**
* Matches the readPacketData(DataInputStream) method in Packet.
*/
private static FuzzyMethodContract READ_PACKET = FuzzyMethodContract.newBuilder().
returnTypeVoid().
parameterDerivedOf(DataInput.class).
parameterCount(1).
build();
private static PacketClassLookup lookup;
// The packet filter manager
private ListenerInvoker manager;
// Error reporter
private ErrorReporter reporter;
// Allows us to determine the sender
private PlayerInjectionHandler playerInjection;
// Share callback filter
private CallbackFilter filter;
// Determine if the read packet method was found
private boolean readPacketIntercepted = false;
public ProxyPacketInjector(ListenerInvoker manager, PlayerInjectionHandler playerInjection,
ErrorReporter reporter) throws FieldAccessException {
this.manager = manager;
this.playerInjection = playerInjection;
this.reporter = reporter;
initialize();
}
@Override
public boolean isCancelled(Object packet) {
return ReadPacketModifier.isCancelled(packet);
}
@Override
public void setCancelled(Object packet, boolean cancelled) {
if (cancelled) {
ReadPacketModifier.setOverride(packet, null);
} else {
ReadPacketModifier.removeOverride(packet);
}
}
private void initialize() throws FieldAccessException {
if (lookup == null) {
try {
lookup = new IntHashMapLookup();
} catch (Exception e1) {
try {
lookup = new ArrayLookup();
} catch (Exception e2) {
// Wow
throw new FieldAccessException(e1.getMessage() + ". Workaround failed too.", e2);
}
}
// Should work fine now
}
}
@Override
public void inputBuffersChanged(Set<PacketType> set) {
// No need to do anything
}
@Override
@SuppressWarnings({"rawtypes", "deprecation"})
public boolean addPacketHandler(PacketType type, Set<ListenerOptions> options) {
final int packetID = type.getLegacyId();
if (hasPacketHandler(type))
return false;
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
// 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 = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Class old = PacketRegistry.getPacketClassFromType(type);
// If this packet is not known
if (old == null) {
throw new IllegalStateException("Packet ID " + type + " is not a valid packet type in this version.");
}
// Check for previous injections
if (Factory.class.isAssignableFrom(old)) {
throw new IllegalStateException("Packet " + type + " has already been injected.");
}
if (filter == null) {
readPacketIntercepted = false;
filter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Skip methods defined in Object
if (method.getDeclaringClass().equals(Object.class)) {
return 0;
} else if (READ_PACKET.isMatch(MethodInfo.fromMethod(method), null)) {
readPacketIntercepted = true;
return 1;
} else {
return 2;
}
}
};
}
// Subclass the specific packet class
ex.setSuperclass(old);
ex.setCallbackFilter(filter);
ex.setCallbackTypes(new Class<?>[] { NoOp.class, ReadPacketModifier.class, ReadPacketModifier.class });
Class proxy = ex.createClass();
// Create the proxy handlers
ReadPacketModifier modifierReadPacket = new ReadPacketModifier(packetID, this, reporter, true);
ReadPacketModifier modifierRest = new ReadPacketModifier(packetID, this, reporter, false);
// Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
// Check that we found the read method
if (!readPacketIntercepted) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_FIND_READ_PACKET_METHOD).messageParam(packetID));
}
// Override values
previous.put(packetID, old);
registry.put(proxy, packetID);
overwritten.put(packetID, proxy);
lookup.setLookup(packetID, proxy);
return true;
}
@Override
@SuppressWarnings({"rawtypes", "deprecation"})
public boolean removePacketHandler(PacketType type) {
final int packetID = type.getLegacyId();
if (!hasPacketHandler(type))
return false;
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Class old = previous.get(packetID);
Class proxy = PacketRegistry.getPacketClassFromType(type);
lookup.setLookup(packetID, old);
previous.remove(packetID);
registry.remove(proxy);
overwritten.remove(packetID);
return true;
}
/**
* Determine if the data a packet read must be buffered.
* @param packetId - the packet to check.
* @return TRUE if it does, FALSE otherwise.
*/
@Deprecated
public boolean requireInputBuffers(int packetId) {
return manager.requireInputBuffer(packetId);
}
@SuppressWarnings("deprecation")
@Override
public boolean hasPacketHandler(PacketType type) {
return PacketRegistry.getPreviousPackets().containsKey(type.getLegacyId());
}
@SuppressWarnings("deprecation")
@Override
public Set<PacketType> getPacketHandlers() {
return PacketRegistry.toPacketTypes(PacketRegistry.getPreviousPackets().keySet(), Sender.CLIENT);
}
// Called from the ReadPacketModified monitor
public PacketEvent packetRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
if (playerInjection.canRecievePackets()) {
return playerInjection.handlePacketRecieved(packet, input, buffered);
}
try {
Player client = playerInjection.getPlayerByConnection((DataInputStream) input);
// Never invoke a event if we don't know where it's from
if (client != null) {
return packetRecieved(packet, client, buffered);
} else {
// The timeout elapsed!
reporter.reportDetailed(this, Report.newBuilder(REPORT_UNKNOWN_ORIGIN_FOR_PACKET).messageParam(input, packet.getType()));
return null;
}
} catch (InterruptedException e) {
// We will ignore this - it occurs when a player disconnects
//reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input);
return null;
}
}
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
NetworkMarker marker = buffered != null ? new LegacyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered, packet.getType()) : null;
PacketEvent event = PacketEvent.fromClient(manager, packet, marker, client);
manager.invokePacketRecieving(event);
return event;
}
@Override
@SuppressWarnings({"rawtypes", "deprecation"})
public synchronized void cleanupAll() {
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
// Remove every packet handler
for (Integer id : previous.keySet().toArray(new Integer[0])) {
removePacketHandler(PacketType.findLegacy(id, Sender.CLIENT));
removePacketHandler(PacketType.findLegacy(id, Sender.SERVER));
}
overwritten.clear();
previous.clear();
}
}
@@ -1,186 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.packet;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Map;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.NetworkProcessor;
import com.google.common.collect.MapMaker;
class ReadPacketModifier implements MethodInterceptor {
public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet.");
// A cancel marker
private static final Object CANCEL_MARKER = new Object();
// Common for all packets of the same type
private ProxyPacketInjector packetInjector;
private int packetID;
// Report errors
private ErrorReporter reporter;
private NetworkProcessor processor;
// If this is a read packet data method
private boolean isReadPacketDataMethod;
// Whether or not a packet has been cancelled
private static Map<Object, Object> override = new MapMaker().weakKeys().makeMap();
public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
this.packetID = packetID;
this.packetInjector = packetInjector;
this.reporter = reporter;
this.processor = new NetworkProcessor(reporter);
this.isReadPacketDataMethod = isReadPacketDataMethod;
}
/**
* Remove any packet overrides.
* @param packet - the packet to rever
*/
public static void removeOverride(Object packet) {
override.remove(packet);
}
/**
* Retrieve the packet that overrides the methods of the given packet.
* @param packet - the given packet.
* @return Overriden object.
*/
public static Object getOverride(Object packet) {
return override.get(packet);
}
/**
* Set the packet instance to delegate to instead, or mark the packet as cancelled.
* <p>
* To undo a override, use {@link #removeOverride(Object)}.
* @param packet - the packet.
* @param override - the override method. NULL to cancel this packet.
*/
public static void setOverride(Object packet, Object overridePacket) {
override.put(packet, overridePacket != null ? overridePacket : CANCEL_MARKER);
}
/**
* Determine if the given packet has been cancelled before.
* @param packet - the packet to check.
* @return TRUE if it has been cancelled, FALSE otherwise.
*/
public static boolean isCancelled(Object packet) {
return getOverride(packet) == CANCEL_MARKER;
}
@SuppressWarnings("deprecation")
@Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// Atomic retrieval
Object overridenObject = override.get(thisObj);
Object returnValue = null;
// We need this in order to get the correct player
InputStream input = isReadPacketDataMethod ? (InputStream) args[0] : null;
ByteArrayOutputStream bufferStream = null;
// See if we need to buffer the read data
if (isReadPacketDataMethod && packetInjector.requireInputBuffers(packetID)) {
CaptureInputStream captured = new CaptureInputStream(
input, bufferStream = new ByteArrayOutputStream());
// Swap it with our custom stream
args[0] = new DataInputStream(captured);
}
if (overridenObject != null) {
// This packet has been cancelled
if (overridenObject == CANCEL_MARKER) {
// So, cancel all void methods
if (method.getReturnType().equals(Void.TYPE))
return null;
else // Revert to normal for everything else
overridenObject = thisObj;
}
returnValue = proxy.invokeSuper(overridenObject, args);
} else {
returnValue = proxy.invokeSuper(thisObj, args);
}
// Is this a readPacketData method?
if (isReadPacketDataMethod) {
// Swap back custom stream
args[0] = input;
try {
byte[] buffer = bufferStream != null ? bufferStream.toByteArray() : null;
// Let the people know
PacketType type = PacketType.findLegacy(packetID, Sender.CLIENT);
PacketContainer container = new PacketContainer(type, thisObj);
PacketEvent event = packetInjector.packetRecieved(container, input, buffer);
// Handle override
if (event != null) {
Object result = event.getPacket().getHandle();
if (event.isCancelled()) {
override.put(thisObj, CANCEL_MARKER);
return returnValue;
} else if (!objectEquals(thisObj, result)) {
override.put(thisObj, result);
}
// This is fine - received packets are enqueued in any case
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
processor.invokePostEvent(event, marker);
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft cannot handle this error
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e)
);
}
}
return returnValue;
}
private boolean objectEquals(Object a, Object b) {
return System.identityHashCode(a) != System.identityHashCode(b);
}
}
@@ -1,130 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.packet;
import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.lang.reflect.Method;
import java.util.Map;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.NetworkProcessor;
import com.google.common.collect.MapMaker;
public class WritePacketModifier implements MethodInterceptor {
public static final ReportType REPORT_CANNOT_WRITE_SERVER_PACKET = new ReportType("Cannot write server packet.");
private static class ProxyInformation {
// Marker that contains custom writers
public final Object proxyObject;
public final PacketEvent event;
public final NetworkMarker marker;
public ProxyInformation(Object proxyObject, PacketEvent event, NetworkMarker marker) {
this.proxyObject = proxyObject;
this.event = event;
this.marker = marker;
}
}
private Map<Object, ProxyInformation> proxyLookup = new MapMaker().weakKeys().makeMap();
// Report errors
private final ErrorReporter reporter;
private final NetworkProcessor processor;
// Whether or not this represents the write method
private boolean isWriteMethod;
public WritePacketModifier(ErrorReporter reporter, boolean isWriteMethod) {
this.reporter = reporter;
this.processor = new NetworkProcessor(reporter);
this.isWriteMethod = isWriteMethod;
}
/**
* Associate the given generated instance of a class and the given parameteters.
* @param generatedClass - the generated class.
* @param proxyObject - the object to call from the generated class.
* @param event - the packet event.
* @param marker - the network marker.
*/
public void register(Object generatedClass, Object proxyObject, PacketEvent event, NetworkMarker marker) {
proxyLookup.put(generatedClass, new ProxyInformation(proxyObject, event, marker));
}
@Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
ProxyInformation information = proxyLookup.get(thisObj);
if (information == null) {
// This is really bad - someone forgot to register the proxy
throw new RuntimeException("Cannot find proxy information for " + thisObj);
}
if (isWriteMethod) {
// If every output handler has been removed - ignore everything
if (!information.marker.getOutputHandlers().isEmpty()) {
try {
DataOutput output = (DataOutput) args[0];
// First - we need the initial buffer
ByteArrayOutputStream outputBufferStream = new ByteArrayOutputStream();
proxy.invoke(information.proxyObject, new Object[] { new DataOutputStream(outputBufferStream) });
// Let each handler prepare the actual output
byte[] outputBuffer = processor.processOutput(information.event, information.marker,
outputBufferStream.toByteArray());
// Write that output to the network stream
output.write(outputBuffer);
// We're done
processor.invokePostEvent(information.event, information.marker);
return null;
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft cannot handle this error
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_WRITE_SERVER_PACKET).callerParam(args[0]).error(e)
);
}
}
// Invoke this write method first
proxy.invoke(information.proxyObject, args);
processor.invokePostEvent(information.event, information.marker);
return null;
}
// Default to the super method
return proxy.invoke(information.proxyObject, args);
}
}
@@ -1,193 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
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.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
/**
* The array list that notifies when packets are sent by the server.
*
* @author Kristian
*/
class InjectedArrayList extends ArrayList<Object> {
public static final ReportType REPORT_CANNOT_REVERT_CANCELLED_PACKET = new ReportType("Reverting cancelled packet failed.");
/**
* Silly Eclipse.
*/
private static final long serialVersionUID = -1173865905404280990L;
// Fake inverted proxy objects
private static ConcurrentMap<Object, Object> delegateLookup = new MapMaker().weakKeys().makeMap();
private transient PlayerInjector injector;
private transient Set<Object> ignoredPackets;
private transient InvertedIntegerCallback callback;
public InjectedArrayList(PlayerInjector injector, Set<Object> ignoredPackets) {
this.injector = injector;
this.ignoredPackets = ignoredPackets;
this.callback = new InvertedIntegerCallback();
}
@Override
public boolean add(Object packet) {
Object result = null;
// Check for fake packets and ignored packets
if (packet instanceof FakePacket) {
return true;
} else if (ignoredPackets.contains(packet)) {
// Don't send it to the filters
result = ignoredPackets.remove(packet);
} else {
result = injector.handlePacketSending(packet);
}
// A NULL packet indicate cancelling
try {
if (result != null) {
super.add(result);
} else {
// We'll use the FakePacket marker instead of preventing the filters
injector.sendServerPacket(createNegativePacket(packet), null, true);
}
// Collection.add contract
return true;
} catch (InvocationTargetException e) {
// Prefer to report this to the user, instead of risking sending it to Minecraft
ProtocolLibrary.getErrorReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_REVERT_CANCELLED_PACKET).error(e).callerParam(packet)
);
// Failure
return false;
}
}
/**
* Used by a hack that reverses the effect of a cancelled packet. Returns a packet
* whereby every int method's return value is inverted (a => -a).
*
* @param source - packet to invert.
* @return The inverted packet.
*/
@SuppressWarnings("deprecation")
Object createNegativePacket(Object source) {
ListenerInvoker invoker = injector.getInvoker();
PacketType type = invoker.getPacketType(source);
// We want to subtract the byte amount that were added to the running
// total of outstanding packets. Otherwise, cancelling too many packets
// might cause a "disconnect.overflow" error.
//
// We do that by constructing a special packet of the same type that returns
// a negative integer for all zero-parameter integer methods. This includes the
// size() method, which is used by the queue method to count the number of
// bytes to add.
//
// Essentially, we have:
//
// public class NegativePacket extends [a packet] {
// @Override
// public int size() {
// return -super.size();
// }
// ect.
// }
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
ex.setSuperclass(MinecraftReflection.getPacketClass());
ex.setInterfaces(new Class[] { FakePacket.class } );
ex.setUseCache(true);
ex.setCallbackType(InvertedIntegerCallback.class);
Class<?> proxyClass = ex.createClass();
Enhancer.registerCallbacks(proxyClass, new Callback[] { callback });
try {
// Temporarily associate the fake packet class
invoker.registerPacketClass(proxyClass, type.getLegacyId());
Object proxy = proxyClass.newInstance();
InjectedArrayList.registerDelegate(proxy, source);
return proxy;
} catch (Exception e) {
// Don't pollute the throws tree
throw new RuntimeException("Cannot create fake class.", e);
} finally {
// Remove this association
invoker.unregisterPacketClass(proxyClass);
}
}
/**
* Ensure that the inverted integer proxy uses the given object as source.
* @param proxy - inverted integer proxy.
* @param source - source object.
*/
private static void registerDelegate(Object proxy, Object source) {
delegateLookup.put(proxy, source);
}
/**
* Inverts the integer result of every integer method.
* @author Kristian
*/
private class InvertedIntegerCallback implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
final Object delegate = delegateLookup.get(obj);
if (delegate == null) {
throw new IllegalStateException("Unable to find delegate source for " + obj);
}
if (method.getReturnType().equals(int.class) && args.length == 0) {
Integer result = (Integer) proxy.invoke(delegate, args);
return -result;
} else {
return proxy.invoke(delegate, args);
}
}
}
}
@@ -1,442 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Used to ensure that the 1.3 server is referencing the correct server handler.
*
* @author Kristian
*/
public class InjectedServerConnection {
// A number of things can go wrong ...
public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
private static Field listenerThreadField;
private static Field minecraftServerField;
private static Field listField;
private static Field dedicatedThreadField;
private static Method serverConnectionMethod;
private List<VolatileField> listFields;
private List<ReplacedArrayList<Object>> replacedLists;
// The current detected server socket
public enum ServerSocketType {
SERVER_CONNECTION,
LISTENER_THREAD,
}
// Used to inject net handlers
private NetLoginInjector netLoginInjector;
// Inject server connections
private AbstractInputStreamLookup socketInjector;
// Detected by the initializer
private ServerSocketType socketType;
private Server server;
private ErrorReporter reporter;
private boolean hasAttempted;
private boolean hasSuccess;
private Object minecraftServer = null;
public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
this.reporter = reporter;
this.server = server;
this.socketInjector = socketInjector;
this.netLoginInjector = netLoginInjector;
}
/**
* Retrieve the current server connection.
* @param reporter - error reproter.
* @param server - the current server.
* @return The current server connection, or NULL if it hasn't been initialized yet.
* @throws FieldAccessException Reflection error.
*/
public static Object getServerConnection(ErrorReporter reporter, Server server) {
try {
// Now we are probably able to check for Netty
InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null);
return inspector.getServerConnection();
} catch (IllegalAccessException e) {
throw new FieldAccessException("Reflection error.", e);
} catch (IllegalArgumentException e) {
throw new FieldAccessException("Corrupt data.", e);
} catch (InvocationTargetException e) {
throw new FieldAccessException("Minecraft error.", e);
}
}
/**
* Initial reflective detective work. Will be automatically called by most methods in this class.
*/
public void initialize() {
// Only execute this method once
if (!hasAttempted)
hasAttempted = true;
else
return;
if (minecraftServerField == null)
minecraftServerField = FuzzyReflection.fromObject(server, true).
getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass());
try {
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
return;
}
try {
if (serverConnectionMethod == null)
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
getMethodByParameters("getServerConnection",
MinecraftReflection.getServerConnectionClass(), new Class[] {});
// We're using Minecraft 1.3.1
socketType = ServerSocketType.SERVER_CONNECTION;
} catch (IllegalArgumentException e) {
// Minecraft 1.2.5 or lower
socketType = ServerSocketType.LISTENER_THREAD;
} catch (Exception e) {
// Oh damn - inform the player
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
}
}
/**
* Retrieve the known server socket type.
* <p>
* This depends on the version of CraftBukkit we are using.
* @return The server socket type.
*/
public ServerSocketType getServerSocketType() {
return socketType;
}
/**
* Inject the connection interceptor into the correct server socket implementation.
*/
public void injectList() {
initialize();
if (socketType == ServerSocketType.SERVER_CONNECTION) {
injectServerConnection();
} else if (socketType == ServerSocketType.LISTENER_THREAD) {
injectListenerThread();
} else {
// Damn it
throw new IllegalStateException("Unable to detected server connection.");
}
}
/**
* Retrieve the listener thread field.
*/
private void initializeListenerField() {
if (listenerThreadField == null)
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
}
/**
* Retrieve the listener thread object, or NULL the server isn't using this socket implementation.
* @return The listener thread, or NULL.
* @throws IllegalAccessException Cannot access field.
* @throws RuntimeException Unexpected class structure - the field doesn't exist.
*/
public Object getListenerThread() throws RuntimeException, IllegalAccessException {
initialize();
if (socketType == ServerSocketType.LISTENER_THREAD) {
initializeListenerField();
return listenerThreadField.get(minecraftServer);
} else {
return null;
}
}
/**
* Retrieve the server connection object, or NULL if the server isn't using it as the socket implementation.
* @return The socket connection, or NULL.
* @throws IllegalAccessException If the reflective operation failed.
* @throws IllegalArgumentException If the reflective operation failed.
* @throws InvocationTargetException If the reflective operation failed.
*/
public Object getServerConnection() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
initialize();
if (socketType == ServerSocketType.SERVER_CONNECTION)
return serverConnectionMethod.invoke(minecraftServer);
else
return null;
}
private void injectListenerThread() {
try {
initializeListenerField();
} catch (RuntimeException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
);
return;
}
Object listenerThread = null;
// Attempt to get the thread
try {
listenerThread = getListenerThread();
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
return;
}
// Inject the server socket too
injectServerSocket(listenerThread);
// Just inject every list field we can get
injectEveryListField(listenerThread, 1);
hasSuccess = true;
}
private void injectServerConnection() {
Object serverConnection = null;
// Careful - we might fail
try {
serverConnection = getServerConnection();
} catch (Exception e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
);
return;
}
if (listField == null)
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
getFieldByType("netServerHandlerList", List.class);
if (dedicatedThreadField == null) {
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).
getFieldListByType(Thread.class);
// Verify the field count
if (matches.size() != 1)
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
);
else
dedicatedThreadField = matches.get(0);
}
// Next, try to get the dedicated thread
try {
if (dedicatedThreadField != null) {
Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
// Inject server socket and NetServerHandlers.
injectServerSocket(dedicatedThread);
injectEveryListField(dedicatedThread, 1);
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
}
injectIntoList(serverConnection, listField);
hasSuccess = true;
}
private void injectServerSocket(Object container) {
socketInjector.inject(container);
}
/**
* Automatically inject into every List-compatible public or private field of the given object.
* @param container - container object with the fields to inject.
* @param minimum - the minimum number of fields we expect exists.
*/
private void injectEveryListField(Object container, int minimum) {
// Ok, great. Get every list field
List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
for (Field list : lists) {
injectIntoList(container, list);
}
// Warn about unexpected errors
if (lists.size() < minimum) {
reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
}
}
@SuppressWarnings("unchecked")
private void injectIntoList(Object instance, Field field) {
VolatileField listFieldRef = new VolatileField(field, instance, true);
List<Object> list = (List<Object>) listFieldRef.getValue();
// Careful not to inject twice
if (list instanceof ReplacedArrayList) {
replacedLists.add((ReplacedArrayList<Object>) list);
} else {
ReplacedArrayList<Object> injectedList = createReplacement(list);
replacedLists.add(injectedList);
listFieldRef.setValue(injectedList);
listFields.add(listFieldRef);
}
}
// Hack to avoid the "moved to quickly" error
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
return new ReplacedArrayList<Object>(list) {
/**
* Shut up Eclipse!
*/
private static final long serialVersionUID = 2070481080950500367L;
// Object writer we'll use
private final ObjectWriter writer = new ObjectWriter();
@Override
protected void onReplacing(Object inserting, Object replacement) {
// Is this a normal Minecraft object?
if (!(inserting instanceof Factory)) {
// If so, copy the content of the old element to the new
try {
writer.copyTo(inserting, replacement, inserting.getClass());
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(InjectedServerConnection.this,
Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
);
}
}
}
@Override
protected void onInserting(Object inserting) {
// Ready for some login handler injection?
if (MinecraftReflection.isLoginHandler(inserting)) {
Object replaced = netLoginInjector.onNetLoginCreated(inserting);
// Only replace if it has changed
if (inserting != replaced)
addMapping(inserting, replaced, true);
}
}
@Override
protected void onRemoved(Object removing) {
// Clean up?
if (MinecraftReflection.isLoginHandler(removing)) {
netLoginInjector.cleanup(removing);
}
}
};
}
/**
* Replace the server handler instance kept by the "keep alive" object.
* @param oldHandler - old server handler.
* @param newHandler - new, proxied server handler.
*/
public void replaceServerHandler(Object oldHandler, Object newHandler) {
if (!hasAttempted) {
injectList();
}
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.addMapping(oldHandler, newHandler);
}
}
}
/**
* Revert to the old vanilla server handler, if it has been replaced.
* @param oldHandler - old vanilla server handler.
*/
public void revertServerHandler(Object oldHandler) {
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.removeMapping(oldHandler);
}
}
}
/**
* Undoes everything.
*/
public void cleanupAll() {
if (replacedLists.size() > 0) {
// Repair the underlying lists
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.revertAll();
}
for (VolatileField field : listFields) {
field.revertValue();
}
listFields.clear();
replacedLists.clear();
}
}
}
@@ -1,162 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.util.concurrent.ConcurrentMap;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;
/**
* Injects every NetLoginHandler created by the server.
*
* @author Kristian
*/
class NetLoginInjector {
public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
// Handles every hook
private ProxyPlayerInjectionHandler injectionHandler;
// Create temporary players
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
// The current error reporter
private ErrorReporter reporter;
private Server server;
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
this.reporter = reporter;
this.server = server;
this.injectionHandler = injectionHandler;
}
/**
* Invoked when a NetLoginHandler has been created.
* @param inserting - the new NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
*/
public Object onNetLoginCreated(Object inserting) {
try {
// Make sure we actually need to inject during this phase
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
return inserting;
Player temporary = playerFactory.createTemporaryPlayer(server);
// Note that we bail out if there's an existing player injector
PlayerInjector injector = injectionHandler.injectPlayer(
temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
if (injector != null) {
// Update injector as well
TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
injector.updateOnLogin = true;
// Save the login
injectedLogins.putIfAbsent(inserting, injector);
}
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
return inserting;
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft can't handle this, so we'll deal with it here
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
messageParam(MinecraftReflection.getNetLoginHandlerName()).
callerParam(inserting, injectionHandler).
error(e)
);
return inserting;
}
}
/**
* Invoked when a NetLoginHandler should be reverted.
* @param inserting - the original NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
*/
public synchronized void cleanup(Object removing) {
PlayerInjector injected = injectedLogins.get(removing);
if (injected != null) {
try {
PlayerInjector newInjector = null;
Player player = injected.getPlayer();
// Clean up list
injectedLogins.remove(removing);
// No need to clean up twice
if (injected.isClean())
return;
// Hack to clean up other references
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
injectionHandler.uninjectPlayer(player);
// Update NetworkManager
if (newInjector != null) {
if (injected instanceof NetworkObjectInjector) {
newInjector.setNetworkManager(injected.getNetworkManager(), true);
}
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Don't leak this to Minecraft
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
messageParam(MinecraftReflection.getNetLoginHandlerName()).
callerParam(removing).
error(e)
);
}
}
}
/**
* Remove all injected hooks.
*/
public void cleanupAll() {
for (PlayerInjector injector : injectedLogins.values()) {
injector.cleanupAll();
}
injectedLogins.clear();
}
}
@@ -1,221 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.Sets;
/**
* Injection hook that overrides the packet queue lists in NetworkHandler.
*
* @author Kristian
*/
class NetworkFieldInjector extends PlayerInjector {
/**
* Marker interface that indicates a packet is fake and should not be processed.
* @author Kristian
*/
public interface FakePacket {
// Nothing
}
// After commit 336a4e00668fd2518c41242755ed6b3bdc3b0e6c (Update CraftBukkit to Minecraft 1.4.4.),
// CraftBukkit stopped redirecting map chunk and map chunk bulk packets to a separate queue.
// Thus, NetworkFieldInjector can safely handle every packet (though not perfectly - some packets
// will be slightly processed).
private MinecraftVersion safeVersion = new MinecraftVersion("1.4.4");
// Packets to ignore
private Set<Object> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Object, Boolean>());
// Overridden fields
private List<VolatileField> overridenLists = new ArrayList<VolatileField>();
// Sync field
private static Field syncField;
private Object syncObject;
// Determine if we're listening
private IntegerSet sendingFilters;
public NetworkFieldInjector(ErrorReporter reporter, Player player, ListenerInvoker manager, IntegerSet sendingFilters) {
super(reporter, player, manager);
this.sendingFilters = sendingFilters;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public synchronized void initialize(Object injectionSource) throws IllegalAccessException {
super.initialize(injectionSource);
// Get the sync field as well
if (hasInitialized) {
if (syncField == null)
syncField = FuzzyReflection.fromObject(networkManager, true).getFieldByType("java\\.lang\\.Object");
syncObject = FieldUtils.readField(syncField, networkManager, true);
}
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
if (networkManager != null) {
try {
if (!filtered) {
ignoredPackets.add(packet);
}
if (marker != null) {
queuedMarkers.put(packet, marker);
}
// Note that invocation target exception is a wrapper for a checked exception
queueMethod.invoke(networkManager, 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.");
}
}
@Override
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
if (version != null && version.compareTo(safeVersion) > 0) {
return null;
} else {
@SuppressWarnings("deprecation")
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
}
}
@Override
public void injectManager() {
if (networkManager != null) {
@SuppressWarnings("rawtypes")
StructureModifier<List> list = networkModifier.withType(List.class);
// Subclass both send queues
for (Field field : list.getFields()) {
VolatileField overwriter = new VolatileField(field, networkManager, true);
@SuppressWarnings("unchecked")
List<Object> minecraftList = (List<Object>) overwriter.getOldValue();
synchronized(syncObject) {
// The list we'll be inserting
List<Object> hackedList = new InjectedArrayList(this, ignoredPackets);
// Add every previously stored packet
for (Object packet : minecraftList) {
hackedList.add(packet);
}
// Don' keep stale packets around
minecraftList.clear();
overwriter.setValue(Collections.synchronizedList(hackedList));
}
overridenLists.add(overwriter);
}
}
}
@Override
@SuppressWarnings("unchecked")
protected void cleanHook() {
// Clean up
for (VolatileField overriden : overridenLists) {
List<Object> minecraftList = (List<Object>) overriden.getOldValue();
List<Object> hacketList = (List<Object>) overriden.getValue();
if (minecraftList == hacketList) {
return;
}
// Get a lock before we modify the list
synchronized(syncObject) {
try {
// Copy over current packets
for (Object packet : (List<Object>) overriden.getValue()) {
minecraftList.add(packet);
}
} finally {
overriden.revertValue();
}
}
}
overridenLists.clear();
}
@Override
public void handleDisconnect() {
// No need to do anything
}
@Override
public boolean canInject(GamePhase phase) {
// All phases should work
return true;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_HANDLER_FIELDS;
}
}
@@ -1,230 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.LazyLoader;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftVersion;
/**
* Injection method that overrides the NetworkHandler itself, and its queue-method.
*
* @author Kristian
*/
public class NetworkObjectInjector extends PlayerInjector {
// Determine if we're listening
private IntegerSet sendingFilters;
// After commit 336a4e00668fd2518c41242755ed6b3bdc3b0e6c (Update CraftBukkit to Minecraft 1.4.4.),
// CraftBukkit stopped redirecting map chunk and map chunk bulk packets to a separate queue.
// Thus, NetworkFieldInjector can safely handle every packet (though not perfectly - some packets
// will be slightly processed).
private MinecraftVersion safeVersion = new MinecraftVersion("1.4.4");
// Shared callback filter - avoid creating a new class every time
private volatile static CallbackFilter callbackFilter;
// Temporary player factory
private static volatile TemporaryPlayerFactory tempPlayerFactory;
/**
* Create a new network object injector.
* <p>
* Note: This class is intended to be internal. Do not use.
* @param reporter - the error reporter.
* @param player - the player Bukkit entity.
* @param invoker - the packet invoker.
* @param sendingFilters - list of permitted packet IDs.
* @throws IllegalAccessException If reflection failed.
*/
public NetworkObjectInjector(ErrorReporter reporter, Player player,
ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException {
super(reporter, player, invoker);
this.sendingFilters = sendingFilters;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
/**
* Create a temporary player for use during login.
* @param server - Bukkit server.
* @return The temporary player.
*/
public Player createTemporaryPlayer(Server server) {
if (tempPlayerFactory == null)
tempPlayerFactory = new TemporaryPlayerFactory();
// Create and associate the fake player with this network injector
return tempPlayerFactory.createTemporaryPlayer(server, this);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue();
if (networkDelegate != null) {
try {
if (marker != null) {
queuedMarkers.put(packet, marker);
}
// 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.");
}
}
@Override
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
if (version != null && version.compareTo(safeVersion) > 0) {
return null;
} else {
@SuppressWarnings("deprecation")
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_OBJECT_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
}
}
@Override
public void injectManager() {
if (networkManager != null) {
final Class<?> networkInterface = networkManagerRef.getField().getType();
final Object networkDelegate = networkManagerRef.getOldValue();
if (!networkInterface.isInterface()) {
throw new UnsupportedOperationException(
"Must use CraftBukkit 1.3.0 or later to inject into into NetworkMananger.");
}
Callback queueFilter = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object packet = args[0];
if (packet != null) {
packet = handlePacketSending(packet);
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
}
// Delegate to our underlying class
return proxy.invokeSuper(networkDelegate, args);
}
};
Callback dispatch = new LazyLoader() {
@Override
public Object loadObject() throws Exception {
return networkDelegate;
}
};
// Share callback filter - that way, we avoid generating a new class every time.
if (callbackFilter == null) {
callbackFilter = new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.equals(queueMethod))
return 0;
else
return 1;
}
};
}
// Create our proxy object
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
ex.setSuperclass(networkInterface);
ex.setCallbacks(new Callback[] { queueFilter, dispatch });
ex.setCallbackFilter(callbackFilter);
// Inject it, if we can.
networkManagerRef.setValue(ex.create());
}
}
@Override
protected void cleanHook() {
// Clean up
if (networkManagerRef != null && networkManagerRef.isCurrentSet()) {
networkManagerRef.revertValue();
}
}
@Override
public void handleDisconnect() {
// No need to do anything
}
@Override
public boolean canInject(GamePhase phase) {
// Works for all phases
return true;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_MANAGER_OBJECT;
}
}
@@ -1,367 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
/**
* Represents a player hook into the NetServerHandler class.
*
* @author Kristian
*/
class NetworkServerInjector extends PlayerInjector {
// Disconnected field
public static final ReportType REPORT_ASSUMING_DISCONNECT_FIELD = new ReportType("Unable to find 'disconnected' field. Assuming %s.");
public static final ReportType REPORT_DISCONNECT_FIELD_MISSING = new ReportType("Cannot find disconnected field. Is ProtocolLib up to date?");
public static final ReportType REPORT_DISCONNECT_FIELD_FAILURE = new ReportType("Unable to update disconnected field. Player quit event may be sent twice.");
private volatile static CallbackFilter callbackFilter;
private volatile static boolean foundSendPacket;
private volatile static Field disconnectField;
private InjectedServerConnection serverInjection;
// Determine if we're listening
private IntegerSet sendingFilters;
// Whether or not the player has disconnected
private boolean hasDisconnected;
// Used to copy fields
private final ObjectWriter writer = new ObjectWriter();
public NetworkServerInjector(
ErrorReporter reporter, Player player,
ListenerInvoker invoker, IntegerSet sendingFilters,
InjectedServerConnection serverInjection) {
super(reporter, player, invoker);
this.sendingFilters = sendingFilters;
this.serverInjection = serverInjection;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
if (serverDelegate != null) {
try {
if (marker != null) {
queuedMarkers.put(packet, marker);
}
// Note that invocation target exception is a wrapper for a checked exception
MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access send packet method.", e);
}
} else {
throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
}
}
@Override
public void injectManager() {
if (serverHandlerRef == null)
throw new IllegalStateException("Cannot find server handler.");
// Don't inject twice
if (serverHandlerRef.getValue() instanceof Factory)
return;
if (!tryInjectManager()) {
Class<?> serverHandlerClass = MinecraftReflection.getPlayerConnectionClass();
// Try to override the proxied object
if (proxyServerField != null) {
serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
serverHandler = serverHandlerRef.getValue();
if (serverHandler == null)
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
else
serverHandlerClass = serverHandler.getClass();
// Try again
if (tryInjectManager()) {
// It worked - probably
return;
}
}
throw new RuntimeException(
"Cannot hook player: Unable to find a valid constructor for the "
+ serverHandlerClass.getName() + " object.");
}
}
private boolean tryInjectManager() {
Class<?> serverClass = serverHandler.getClass();
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
Callback sendPacketCallback = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object packet = args[0];
if (packet != null) {
packet = handlePacketSending(packet);
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
}
// Call the method directly
return proxy.invokeSuper(obj, args);
};
};
Callback noOpCallback = NoOp.INSTANCE;
// Share callback filter - that way, we avoid generating a new class for
// every logged in player.
if (callbackFilter == null) {
callbackFilter = new SendMethodFilter();
}
ex.setSuperclass(serverClass);
ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
ex.setCallbackFilter(callbackFilter);
// Find the Minecraft NetServerHandler superclass
Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
DefaultInstances serverInstances = null;
// Maybe the proxy instance can help?
Object proxyInstance = getProxyServerHandler();
// Use the existing server proxy when we create one
if (proxyInstance != null && proxyInstance != serverHandler) {
serverInstances = DefaultInstances.fromArray(generator,
ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
} else {
serverInstances = DefaultInstances.fromArray(generator);
}
serverInstances.setNonNull(true);
serverInstances.setMaximumRecursion(1);
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
// Inject it now
if (proxyObject != null) {
// Did we override a sendPacket method?
if (!foundSendPacket) {
throw new IllegalArgumentException("Unable to find a sendPacket method in " + serverClass);
}
serverInjection.replaceServerHandler(serverHandler, proxyObject);
serverHandlerRef.setValue(proxyObject);
return true;
} else {
return false;
}
}
private Object getProxyServerHandler() {
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
try {
return FieldUtils.readField(proxyServerField, serverHandler, true);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Oh well
}
}
return null;
}
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
if (MinecraftReflection.isMinecraftClass(clazz))
return clazz;
else if (clazz.equals(Object.class))
return clazz;
else
return getFirstMinecraftSuperClass(clazz.getSuperclass());
}
@Override
protected void cleanHook() {
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
serverHandlerRef.revertValue();
try {
if (getNetHandler() != null) {
// Restore packet listener
try {
FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
} catch (IllegalAccessException e) {
// Oh well
e.printStackTrace();
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// Prevent the PlayerQuitEvent from being sent twice
if (hasDisconnected) {
setDisconnect(serverHandlerRef.getValue(), true);
}
}
serverInjection.revertServerHandler(serverHandler);
}
@Override
public void handleDisconnect() {
hasDisconnected = true;
}
/**
* Set the disconnected field in a NetServerHandler.
* @param handler - the NetServerHandler.
* @param value - the new value.
*/
private void setDisconnect(Object handler, boolean value) {
// Set it
try {
// Load the field
if (disconnectField == null) {
disconnectField = FuzzyReflection.fromObject(handler).getFieldByName("disconnected.*");
}
FieldUtils.writeField(disconnectField, handler, value);
} catch (IllegalArgumentException e) {
// Assume it's the first ...
if (disconnectField == null) {
disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class);
reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUMING_DISCONNECT_FIELD).messageParam(disconnectField));
// Try again
if (disconnectField != null) {
setDisconnect(handler, value);
return;
}
}
// This is really bad
reporter.reportDetailed(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_MISSING).error(e));
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_FAILURE).error(e));
}
}
@Override
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
// We support everything
return null;
}
@Override
public boolean canInject(GamePhase phase) {
// Doesn't work when logging in
return phase == GamePhase.PLAYING;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
/**
* Represents a CallbackFilter that only matches the SendPacket method.
* @author Kristian
*/
private static class SendMethodFilter implements CallbackFilter {
private Method sendPacket = MinecraftMethods.getSendPacketMethod();
@Override
public int accept(Method method) {
if (isCallableEqual(sendPacket, method)) {
NetworkServerInjector.foundSendPacket = true;
return 0;
} else {
return 1;
}
}
/**
* Determine if the two methods are equal in terms of call semantics.
* <p>
* Two methods are equal if they have the same name, parameter types and return type.
* @param first - first method.
* @param second - second method.
* @return TRUE if they are, FALSE otherwise.
*/
private boolean isCallableEqual(Method first, Method second) {
return first.getName().equals(second.getName()) &&
first.getReturnType().equals(second.getReturnType()) &&
Arrays.equals(first.getParameterTypes(), second.getParameterTypes());
}
}
}
@@ -1,760 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import net.sf.cglib.proxy.Factory;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.injector.packet.InterceptWritePacket;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.MapMaker;
public abstract class PlayerInjector implements SocketInjector {
// Disconnect method related reports
public static final ReportType REPORT_ASSUME_DISCONNECT_METHOD = new ReportType("Cannot find disconnect method by name. Assuming %s.");
public static final ReportType REPORT_INVALID_ARGUMENT_DISCONNECT = new ReportType("Invalid argument passed to disconnect method: %s");
public static final ReportType REPORT_CANNOT_ACCESS_DISCONNECT = new ReportType("Unable to access disconnect method.");
public static final ReportType REPORT_CANNOT_CLOSE_SOCKET = new ReportType("Unable to close socket.");
public static final ReportType REPORT_ACCESS_DENIED_CLOSE_SOCKET = new ReportType("Insufficient permissions. Cannot close socket.");
public static final ReportType REPORT_DETECTED_CUSTOM_SERVER_HANDLER =
new ReportType("Detected server handler proxy type by another plugin. Conflict may occur!");
public static final ReportType REPORT_CANNOT_PROXY_SERVER_HANDLER = new ReportType("Unable to load server handler from proxy type.");
public static final ReportType REPORT_CANNOT_UPDATE_PLAYER = new ReportType("Cannot update player in PlayerEvent.");
public static final ReportType REPORT_CANNOT_HANDLE_PACKET = new ReportType("Cannot handle server packet.");
public static final ReportType REPORT_INVALID_NETWORK_MANAGER = new ReportType("NetworkManager doesn't appear to be valid.");
// Net login handler stuff
private static Field netLoginNetworkField;
// Different disconnect methods
private static Method loginDisconnect;
private static Method serverDisconnect;
// Cache previously retrieved fields
protected static Field serverHandlerField;
protected static Field proxyServerField;
protected static Field networkManagerField;
protected static Field netHandlerField;
protected static Field socketField;
protected static Field socketAddressField;
private static Field inputField;
private static Field entityPlayerField;
// Whether or not we're using a proxy type
private static boolean hasProxyType;
// To add our injected array lists
protected static StructureModifier<Object> networkModifier;
// And methods
protected static Method queueMethod;
protected static Method processMethod;
protected volatile Player player;
protected boolean hasInitialized;
// Reference to the player's network manager
protected VolatileField networkManagerRef;
protected VolatileField serverHandlerRef;
protected Object networkManager;
// Current net handler
protected Object loginHandler;
protected Object serverHandler;
protected Object netHandler;
// Current socket and address
protected Socket socket;
protected SocketAddress socketAddress;
// The packet manager and filters
protected ListenerInvoker invoker;
// Previous data input
protected DataInputStream cachedInput;
// Handle errors
protected ErrorReporter reporter;
// Previous markers
protected Map<Object, NetworkMarker> queuedMarkers = new MapMaker().weakKeys().makeMap();
protected InterceptWritePacket writePacketInterceptor;
// Whether or not the injector has been cleaned
private boolean clean;
// Whether or not to update the current player on the first Packet1Login
boolean updateOnLogin;
volatile Player updatedPlayer;
public PlayerInjector(ErrorReporter reporter, Player player, ListenerInvoker invoker) {
this.reporter = reporter;
this.player = player;
this.invoker = invoker;
this.writePacketInterceptor = invoker.getInterceptWritePacket();
}
/**
* Retrieve the notch (NMS) entity player object.
* @param player - the player to retrieve.
* @return Notch player object.
*/
protected Object getEntityPlayer(Player player) {
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
return unwrapper.unwrapItem(player);
}
/**
* Initialize all fields for this player injector, if it hasn't already.
* @param injectionSource - Injection source
* @throws IllegalAccessException An error has occured.
*/
public void initialize(Object injectionSource) throws IllegalAccessException {
if (injectionSource == null)
throw new IllegalArgumentException("injectionSource cannot be NULL");
//Dispatch to the correct injection method
if (injectionSource instanceof Player)
initializePlayer((Player) injectionSource);
else if (MinecraftReflection.isLoginHandler(injectionSource))
initializeLogin(injectionSource);
else
throw new IllegalArgumentException("Cannot initialize a player hook using a " + injectionSource.getClass().getName());
}
/**
* Initialize the player injector using an actual player instance.
* @param player - the player to hook.
*/
public void initializePlayer(Player player) {
Object notchEntity = getEntityPlayer(player);
// Save the player too
this.player = player;
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", MinecraftReflection.getPlayerConnectionClass());
proxyServerField = getProxyField(notchEntity, serverHandlerField);
}
// Yo dawg
serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
serverHandler = serverHandlerRef.getValue();
// Next, get the network manager
if (networkManagerField == null)
networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(
"networkManager", MinecraftReflection.getNetworkManagerClass());
initializeNetworkManager(networkManagerField, serverHandler);
}
}
/**
* Initialize the player injector from a NetLoginHandler.
* @param netLoginHandler - the net login handler to inject.
*/
public void initializeLogin(Object netLoginHandler) {
if (!hasInitialized) {
// Just in case
if (!MinecraftReflection.isLoginHandler(netLoginHandler))
throw new IllegalArgumentException("netLoginHandler (" + netLoginHandler + ") is not a " +
MinecraftReflection.getNetLoginHandlerName());
hasInitialized = true;
loginHandler = netLoginHandler;
if (netLoginNetworkField == null)
netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler).
getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass());
initializeNetworkManager(netLoginNetworkField, netLoginHandler);
}
}
private void initializeNetworkManager(Field reference, Object container) {
networkManagerRef = new VolatileField(reference, container);
networkManager = networkManagerRef.getValue();
// No, don't do it
if (networkManager instanceof Factory) {
return;
}
// Create the network manager modifier from the actual object type
if (networkManager != null && networkModifier == null)
networkModifier = new StructureModifier<Object>(networkManager.getClass(), null, false);
// And the queue method
if (queueMethod == null)
queueMethod = FuzzyReflection.fromClass(reference.getType()).
getMethodByParameters("queue", MinecraftReflection.getPacketClass());
}
/**
* Retrieve whether or not the server handler is a proxy object.
* @return TRUE if it is, FALSE otherwise.
*/
protected boolean hasProxyServerHandler() {
return hasProxyType;
}
/**
* Retrieve the current network manager.
* @return Current network manager.
*/
public Object getNetworkManager() {
return networkManagerRef.getValue();
}
/**
* Retrieve the current server handler (PlayerConnection).
* @return Current server handler.
*/
public Object getServerHandler() {
return serverHandlerRef.getValue();
}
/**
* Set the current network manager.
* @param value - new network manager.
* @param force - whether or not to save this value.
*/
public void setNetworkManager(Object value, boolean force) {
networkManagerRef.setValue(value);
if (force)
networkManagerRef.saveValue();
initializeNetworkManager(networkManagerField, serverHandler);
}
/**
* Retrieve the associated socket of this player.
* @return The associated socket.
* @throws IllegalAccessException If we're unable to read the socket field.
*/
@Override
public Socket getSocket() throws IllegalAccessException {
try {
if (socketField == null)
socketField = FuzzyReflection.fromObject(networkManager, true).
getFieldListByType(Socket.class).get(0);
if (socket == null)
socket = (Socket) FieldUtils.readField(socketField, networkManager, true);
return socket;
} catch (IndexOutOfBoundsException e) {
throw new IllegalAccessException("Unable to read the socket field.");
}
}
/**
* Retrieve the associated remote address of a player.
* @return The associated remote address.
* @throws IllegalAccessException If we're unable to read the socket address field.
*/
@Override
public SocketAddress getAddress() throws IllegalAccessException {
try {
if (socketAddressField == null)
socketAddressField = FuzzyReflection.fromObject(networkManager, true).
getFieldListByType(SocketAddress.class).get(0);
if (socketAddress == null)
socketAddress = (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true);
return socketAddress;
} catch (IndexOutOfBoundsException e) {
// Inform about the state of the network manager too
reporter.reportWarning(
this, Report.newBuilder(REPORT_INVALID_NETWORK_MANAGER).callerParam(networkManager).build());
throw new IllegalAccessException("Unable to read the socket address field.");
}
}
/**
* Attempt to disconnect the current client.
* @param message - the message to display.
* @throws InvocationTargetException If disconnection failed.
*/
@Override
public void disconnect(String message) throws InvocationTargetException {
// Get a non-null handler
boolean usingNetServer = serverHandler != null;
Object handler = usingNetServer ? serverHandler : loginHandler;
Method disconnect = usingNetServer ? serverDisconnect : loginDisconnect;
// Execute disconnect on it
if (handler != null) {
if (disconnect == null) {
try {
disconnect = FuzzyReflection.fromObject(handler).getMethodByName("disconnect.*");
} catch (IllegalArgumentException e) {
// Just assume it's the first String method
disconnect = FuzzyReflection.fromObject(handler).getMethodByParameters("disconnect", String.class);
reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUME_DISCONNECT_METHOD).messageParam(disconnect));
}
// Save the method for later
if (usingNetServer)
serverDisconnect = disconnect;
else
loginDisconnect = disconnect;
}
try {
disconnect.invoke(handler, message);
return;
} catch (IllegalArgumentException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_INVALID_ARGUMENT_DISCONNECT).error(e).messageParam(message).callerParam(handler));
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_ACCESS_DISCONNECT).error(e));
}
}
// Fuck it
try {
Socket socket = getSocket();
try {
socket.close();
} catch (IOException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CLOSE_SOCKET).error(e).callerParam(socket));
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_ACCESS_DENIED_CLOSE_SOCKET).error(e));
}
}
private Field getProxyField(Object notchEntity, Field serverField) {
try {
Object currentHandler = FieldUtils.readField(serverHandlerField, notchEntity, true);
// This is bad
if (currentHandler == null)
throw new ServerHandlerNull();
// See if this isn't a standard net handler class
if (!isStandardMinecraftNetHandler(currentHandler)) {
// This is our proxy object
if (currentHandler instanceof Factory)
return null;
hasProxyType = true;
reporter.reportWarning(this, Report.newBuilder(REPORT_DETECTED_CUSTOM_SERVER_HANDLER).callerParam(serverField));
// No? Is it a Proxy type?
try {
FuzzyReflection reflection = FuzzyReflection.fromObject(currentHandler, true);
// It might be
return reflection.getFieldByType("NetServerHandler", MinecraftReflection.getPlayerConnectionClass());
} catch (RuntimeException e) {
// Damn
}
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PROXY_SERVER_HANDLER).error(e).callerParam(notchEntity, serverField));
}
// Nope, just go with it
return null;
}
/**
* Determine if a given object is a standard Minecraft net handler.
* @param obj the object to test.
* @return TRUE if it is, FALSE otherwise.
*/
private boolean isStandardMinecraftNetHandler(Object obj) {
if (obj == null)
return false;
Class<?> clazz = obj.getClass();
return MinecraftReflection.getNetLoginHandlerClass().equals(clazz) ||
MinecraftReflection.getPlayerConnectionClass().equals(clazz);
}
/**
* Retrieves the current net handler for this player.
* @return Current net handler.
* @throws IllegalAccessException Unable to find or retrieve net handler.
*/
protected Object getNetHandler() throws IllegalAccessException {
return getNetHandler(false);
}
/**
* Retrieves the current net handler for this player.
* @param refresh - Whether or not to refresh
* @return Current net handler.
* @throws IllegalAccessException Unable to find or retrieve net handler.
* @return The current net handler for this player
*/
protected Object getNetHandler(boolean refresh) throws IllegalAccessException {
// What a mess
try {
if (netHandlerField == null)
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType("NetHandler", MinecraftReflection.getNetHandlerClass());
} catch (RuntimeException e1) {
// Swallow it
}
// Second attempt
if (netHandlerField == null) {
try {
// Well, that sucks. Try just Minecraft objects then.
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType(MinecraftReflection.getMinecraftObjectRegex());
} catch (RuntimeException e2) {
throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
}
}
// Get the handler
if (netHandler == null || refresh)
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
return netHandler;
}
/**
* Retrieve the stored entity player from a given NetHandler.
* @param netHandler - the nethandler to retrieve it from.
* @return The stored entity player.
* @throws IllegalAccessException If the reflection failed.
*/
private Object getEntityPlayer(Object netHandler) throws IllegalAccessException {
if (entityPlayerField == null)
entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType(
"EntityPlayer", MinecraftReflection.getEntityPlayerClass());
return FieldUtils.readField(entityPlayerField, 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(Object packet) throws IllegalAccessException, InvocationTargetException {
Object netHandler = getNetHandler();
// Get the process method
if (processMethod == null) {
try {
processMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
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 marker - the network marker.
* @param filtered - whether or not the packet will be filtered by our listeners.
* @throws InvocationTargetException If an error occured when sending the packet.
*/
@Override
public abstract void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException;
/**
* Inject a hook to catch packets sent to the current player.
*/
public abstract void injectManager();
/**
* Remove all hooks and modifications.
*/
public final void cleanupAll() {
if (!clean) {
cleanHook();
writePacketInterceptor.cleanup();
}
clean = true;
}
/**
* Clean up after the player has disconnected.
*/
public abstract void handleDisconnect();
/**
* Override to add custom cleanup behavior.
*/
protected abstract void cleanHook();
/**
* Determine whether or not this hook has already been cleaned.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClean() {
return clean;
}
/**
* Determine if this inject method can even be attempted.
* @param state - Game phase
* @return TRUE if can be attempted, though possibly with failure, FALSE otherwise.
*/
public abstract boolean canInject(GamePhase state);
/**
* Retrieve the hook type this class represents.
* @return Hook type this class represents.
*/
public abstract PlayerInjectHooks getHookType();
/**
* Invoked before a new listener is registered.
* <p>
* The player injector should only return a non-null value if some or all of the packet IDs are unsupported.
* @param version - the current Minecraft version, or NULL if unknown.
* @param listener - the listener that is about to be registered.
* @return A error message with the unsupported packet IDs, or NULL if this listener is valid.
*/
public abstract UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener);
/**
* Allows a packet to be sent by the listeners.
* @param packet - packet to sent.
* @return The given packet, or the packet replaced by the listeners.
*/
@SuppressWarnings({ "deprecation", "null" })
public Object handlePacketSending(Object packet) {
try {
// Get the packet ID too
Integer id = invoker.getPacketID(packet);
Player currentPlayer = player;
// Hack #1
if (updateOnLogin) {
if (updatedPlayer == null) {
try {
final Object handler = getNetHandler(true);
// Is this a net server class?
if (MinecraftReflection.getPlayerConnectionClass().isAssignableFrom(handler.getClass())) {
setUpdatedPlayer(
(Player) MinecraftReflection.getBukkitEntity(getEntityPlayer(handler))
);
}
} catch (IllegalAccessException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLAYER).error(e).callerParam(packet));
}
}
// This will only occur in the NetLoginHandler injection
if (updatedPlayer != null) {
currentPlayer = updatedPlayer;
updateOnLogin = false;
}
}
// Make sure we're listening
if (id != null && hasListener(id)) {
NetworkMarker marker = queuedMarkers.remove(packet);
// A packet has been sent guys!
PacketType type = PacketType.findLegacy(id, Sender.SERVER);
PacketContainer container = new PacketContainer(type, packet);
PacketEvent event = PacketEvent.fromServer(invoker, container, marker, currentPlayer);
invoker.invokePacketSending(event);
// Cancelling is pretty simple. Just ignore the packet.
if (event.isCancelled())
return null;
// Right, remember to replace the packet again
Object result = event.getPacket().getHandle();
marker = NetworkMarker.getNetworkMarker(event);
// See if we need to proxy the write method
if (result != null && (NetworkMarker.hasOutputHandlers(marker) || NetworkMarker.hasPostListeners(marker))) {
result = writePacketInterceptor.constructProxy(result, event, marker);
}
return result;
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_HANDLE_PACKET).error(e).callerParam(packet));
}
return packet;
}
/**
* Determine if the given injector is listening for this packet ID.
* @param packetID - packet ID to check.
* @return TRUE if it is, FALSE oterhwise.
*/
protected abstract boolean hasListener(int packetID);
/**
* Retrieve the current player's input stream.
* @param cache - whether or not to cache the result of this method.
* @return The player's input stream.
*/
public DataInputStream getInputStream(boolean cache) {
// And the data input stream that we'll use to identify a player
if (networkManager == null)
throw new IllegalStateException("Network manager is NULL.");
if (inputField == null)
inputField = FuzzyReflection.fromObject(networkManager, true).
getFieldByType("java\\.io\\.DataInputStream");
// Get the associated input stream
try {
if (cache && cachedInput != null)
return cachedInput;
// Save to cache
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager, true);
return cachedInput;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read input stream.", e);
}
}
/**
* Retrieve the hooked player.
*/
@Override
public Player getPlayer() {
return player;
}
/**
* Set the hooked player.
* <p>
* Should only be called during the creation of the injector.
* @param player - the new hooked player.
*/
public void setPlayer(Player player) {
this.player = player;
}
/**
* Object that can invoke the packet events.
* @return Packet event invoker.
*/
public ListenerInvoker getInvoker() {
return invoker;
}
/**
* Retrieve the hooked player object OR the more up-to-date player instance.
* @return The hooked player, or a more up-to-date instance.
*/
@Override
public Player getUpdatedPlayer() {
if (updatedPlayer != null)
return updatedPlayer;
else
return player;
}
@Override
public void transferState(SocketInjector delegate) {
// Do nothing
}
@Override
public void setUpdatedPlayer(Player updatedPlayer) {
this.updatedPlayer = updatedPlayer;
}
/**
* Indicates that a player's NetServerHandler or PlayerConnection was NULL.
* <p>
* This is usually because the player has just logged out, or due to it being a "fake" player in MCPC+/Cauldron.
* @author Kristian
*/
public static class ServerHandlerNull extends IllegalAccessError {
private static final long serialVersionUID = 1L;
public ServerHandlerNull() {
super("Unable to fetch server handler: was NUll.");
}
public ServerHandlerNull(String s) {
super(s);
}
}
}
@@ -1,143 +0,0 @@
package com.comphenix.protocol.injector.player;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
/**
* Constructor for different player injectors.
*
* @author Kristian
*/
public class PlayerInjectorBuilder {
public static PlayerInjectorBuilder newBuilder() {
return new PlayerInjectorBuilder();
}
protected PlayerInjectorBuilder() {
// Use the static method.
}
protected ErrorReporter reporter;
protected Predicate<GamePhase> injectionFilter;
protected ListenerInvoker invoker;
protected Set<PacketListener> packetListeners;
protected Server server;
protected MinecraftVersion version;
/**
* The error reporter used by the created injector.
* @param reporter - new error reporter.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
this.reporter = reporter;
return this;
}
/**
* The injection filter that is used to determine if it is necessary to perform
* injection during a certain phase.
* @param injectionFilter - filter predicate.
* @return This builder, for chaining.
*/
@Nonnull
public PlayerInjectorBuilder injectionFilter(@Nonnull Predicate<GamePhase> injectionFilter) {
Preconditions.checkNotNull(injectionFilter, "injectionFilter cannot be NULL");
this.injectionFilter = injectionFilter;
return this;
}
/**
* The packet stream invoker.
* @param invoker - the invoker.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
this.invoker = invoker;
return this;
}
/**
* Set the set of packet listeners.
* @param packetListeners - packet listeners.
* @return This builder, for chaining.
*/
@Nonnull
public PlayerInjectorBuilder packetListeners(@Nonnull Set<PacketListener> packetListeners) {
Preconditions.checkNotNull(packetListeners, "packetListeners cannot be NULL");
this.packetListeners = packetListeners;
return this;
}
/**
* Set the Bukkit server used for scheduling.
* @param server - the Bukkit server.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder server(@Nonnull Server server) {
Preconditions.checkNotNull(server, "server cannot be NULL");
this.server = server;
return this;
}
/**
* Set the current Minecraft version.
* @param version - the current Minecraft version, or NULL if unknown.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder version(MinecraftVersion version) {
this.version = version;
return this;
}
/**
* Called before an object is created with this builder.
*/
private void initializeDefaults() {
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
// Initialize with default values if we can
if (reporter == null)
reporter = ProtocolLibrary.getErrorReporter();
if (invoker == null)
invoker = (PacketFilterManager) manager;
if (server == null)
server = Bukkit.getServer();
if (injectionFilter == null)
throw new IllegalStateException("injectionFilter must be initialized.");
if (packetListeners == null)
throw new IllegalStateException("packetListeners must be initialized.");
}
/**
* Construct the injection handler.
* <p>
* Any builder parameter marked as NON-NULL is essential and must be initialized.
* @return The constructed injection handler using the current parameters.
*/
public PlayerInjectionHandler buildHandler() {
// Fill any default fields
initializeDefaults();
return new ProxyPlayerInjectionHandler(
reporter, injectionFilter,
invoker, packetListeners, server, version);
}
}
@@ -1,763 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import io.netty.channel.Channel;
import java.io.DataInputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.injector.server.BukkitSocketInjector;
import com.comphenix.protocol.injector.server.InputStreamLookupBuilder;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.SafeCacheBuilder;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
/**
* Responsible for injecting into a player's sendPacket method.
*
* @author Kristian
*/
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
// Warnings and errors
public static final ReportType REPORT_UNSUPPPORTED_LISTENER = new ReportType("Cannot fully register listener for %s: %s");
// Fallback to older player hook types
public static final ReportType REPORT_PLAYER_HOOK_FAILED = new ReportType("Player hook %s failed.");
public static final ReportType REPORT_SWITCHED_PLAYER_HOOK = new ReportType("Switching to %s instead.");
public static final ReportType REPORT_HOOK_CLEANUP_FAILED = new ReportType("Cleaing up after player hook failed.");
public static final ReportType REPORT_CANNOT_REVERT_HOOK = new ReportType("Unable to fully revert old injector. May cause conflicts.");
// Server connection injection
private InjectedServerConnection serverInjection;
// Server socket injection
private AbstractInputStreamLookup inputStreamLookup;
// NetLogin injector
private NetLoginInjector netLoginInjector;
// The last successful player hook
private WeakReference<PlayerInjector> lastSuccessfulHook;
// Dummy injection
private ConcurrentMap<Player, PlayerInjector> dummyInjectors =
SafeCacheBuilder.newBuilder().
expireAfterWrite(30, TimeUnit.SECONDS).
build(BlockingHashMap.<Player, PlayerInjector>newInvalidCacheLoader());
// Player injection
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
// Player injection types
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// Error reporter
private ErrorReporter reporter;
// Whether or not we're closing
private boolean hasClosed;
// Used to invoke events
private ListenerInvoker invoker;
// Current Minecraft version
private MinecraftVersion version;
// Enabled packet filters
private IntegerSet sendingFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
// List of packet listeners
private Set<PacketListener> packetListeners;
// Used to filter injection attempts
private Predicate<GamePhase> injectionFilter;
public ProxyPlayerInjectionHandler(
ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server, MinecraftVersion version) {
this.reporter = reporter;
this.invoker = invoker;
this.injectionFilter = injectionFilter;
this.packetListeners = packetListeners;
this.version = version;
this.inputStreamLookup = InputStreamLookupBuilder.newBuilder().
server(server).
reporter(reporter).
build();
// Create net login injectors and the server connection injector
this.netLoginInjector = new NetLoginInjector(reporter, server, this);
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
serverInjection.injectList();
}
@Override
public int getProtocolVersion(Player player) {
// Just use the server version
return MinecraftProtocolVersion.getCurrentVersion();
}
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
@Override
public PlayerInjectHooks getPlayerHook() {
return getPlayerHook(GamePhase.PLAYING);
}
/**
* Retrieves how the server packets are read.
* @param phase - the current game phase.
* @return Injection method for reading server packets.
*/
@Override
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
switch (phase) {
case LOGIN:
return loginPlayerHook;
case PLAYING:
return playingPlayerHook;
default:
throw new IllegalArgumentException("Cannot retrieve injection hook for both phases at the same time.");
}
}
@Override
public boolean hasMainThreadListener(PacketType type) {
return sendingFilters.contains(type.getLegacyId());
}
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
setPlayerHook(GamePhase.PLAYING, playerHook);
}
/**
* Sets how the server packets are read.
* @param phase - the current game phase.
* @param playerHook - the new injection method for reading server packets.
*/
@Override
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
if (phase.hasLogin())
loginPlayerHook = playerHook;
if (phase.hasPlaying())
playingPlayerHook = playerHook;
// Make sure the current listeners are compatible
checkListener(packetListeners);
}
@Override
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
sendingFilters.add(type.getLegacyId());
}
@Override
public void removePacketHandler(PacketType type) {
sendingFilters.remove(type.getLegacyId());
}
/**
* Used to construct a player hook.
* @param player - the player to hook.
* @param hook - the hook type.
* @return A new player hoook
* @throws IllegalAccessException Unable to do our reflection magic.
*/
private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
// Construct the correct player hook
switch (hook) {
case NETWORK_HANDLER_FIELDS:
return new NetworkFieldInjector(reporter, player, invoker, sendingFilters);
case NETWORK_MANAGER_OBJECT:
return new NetworkObjectInjector(reporter, player, invoker, sendingFilters);
case NETWORK_SERVER_OBJECT:
return new NetworkServerInjector(reporter, player, invoker, sendingFilters, serverInjection);
default:
throw new IllegalArgumentException("Cannot construct a player injector.");
}
}
/**
* Retrieve a player by its DataInput connection.
* @param inputStream - the associated DataInput connection.
* @return The player we found.
*/
@Override
public Player getPlayerByConnection(DataInputStream inputStream) {
// Wait until the connection owner has been established
SocketInjector injector = inputStreamLookup.waitSocketInjector(inputStream);
if (injector != null) {
return injector.getPlayer();
} else {
return null;
}
}
/**
* Helper function that retrieves the injector type of a given player injector.
* @param injector - injector type.
* @return The injector type.
*/
private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
}
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This call will be ignored if there's no listener that can receive the given events.
* @param player - player to hook.
* @param strategy - how to handle previous player injections.
*/
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
// Inject using the player instance itself
if (isInjectionNecessary(GamePhase.PLAYING)) {
injectPlayer(player, player, strategy, GamePhase.PLAYING);
}
}
/**
* Determine if it's truly necessary to perform the given player injection.
* @param phase - current game phase.
* @return TRUE if we should perform the injection, FALSE otherwise.
*/
public boolean isInjectionNecessary(GamePhase phase) {
return injectionFilter.apply(phase);
}
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This method will always perform the instructed injection.
*
* @param player - player to hook.
* @param injectionPoint - the object to use during the injection process.
* @param phase - the current game phase.
* @return The resulting player injector, or NULL if the injection failed.
*/
PlayerInjector injectPlayer(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) {
if (player == null)
throw new IllegalArgumentException("Player cannot be NULL.");
if (injectionPoint == null)
throw new IllegalArgumentException("injectionPoint cannot be NULL.");
if (phase == null)
throw new IllegalArgumentException("phase cannot be NULL.");
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
synchronized (player) {
return injectPlayerInternal(player, injectionPoint, stategy, phase);
}
}
// Unsafe variant of the above
private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) {
PlayerInjector injector = playerInjection.get(player);
PlayerInjectHooks tempHook = getPlayerHook(phase);
PlayerInjectHooks permanentHook = tempHook;
// The given player object may be fake, so be careful!
// See if we need to inject something else
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
// Don't inject if the class has closed
if (!hasClosed && (tempHook != getInjectorType(injector) || invalidInjector)) {
while (tempHook != PlayerInjectHooks.NONE) {
// Whether or not the current hook method failed completely
boolean hookFailed = false;
// Remove the previous hook, if any
cleanupHook(injector);
try {
injector = getHookInstance(player, tempHook);
// Make sure this injection method supports the current game phase
if (injector.canInject(phase)) {
injector.initialize(injectionPoint);
// Get socket and socket injector
SocketAddress address = injector.getAddress();
// Ignore logged out players
if (address == null)
return null;
SocketInjector previous = inputStreamLookup.peekSocketInjector(address);
Socket socket = injector.getSocket();
// Close any previously associated hooks before we proceed
if (previous != null && !(player instanceof Factory)) {
switch (stategy) {
case OVERRIDE:
uninjectPlayer(previous.getPlayer(), true);
break;
case BAIL_OUT:
return null;
}
}
injector.injectManager();
saveAddressLookup(address, socket, injector);
break;
}
} catch (PlayerLoggedOutException e) {
throw e;
} catch (Exception e) {
// Mark this injection attempt as a failure
reporter.reportDetailed(this,
Report.newBuilder(REPORT_PLAYER_HOOK_FAILED).messageParam(tempHook).callerParam(player, injectionPoint, phase).error(e)
);
hookFailed = true;
}
// Choose the previous player hook type
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
if (hookFailed)
reporter.reportWarning(this, Report.newBuilder(REPORT_SWITCHED_PLAYER_HOOK).messageParam(tempHook));
// Check for UTTER FAILURE
if (tempHook == PlayerInjectHooks.NONE) {
cleanupHook(injector);
injector = null;
hookFailed = true;
}
// Should we set the default hook method too?
if (hookFailed) {
permanentHook = tempHook;
}
}
// Update values
if (injector != null)
lastSuccessfulHook = new WeakReference<PlayerInjector>(injector);
if (permanentHook != getPlayerHook(phase))
setPlayerHook(phase, tempHook);
// Save injector
if (injector != null) {
playerInjection.put(player, injector);
}
}
return injector;
}
private void saveAddressLookup(SocketAddress address, Socket socket, SocketInjector injector) {
SocketAddress socketAddress = socket != null ? socket.getRemoteSocketAddress() : null;
if (socketAddress != null && !Objects.equal(socketAddress, address)) {
// Save this version as well
inputStreamLookup.setSocketInjector(socketAddress, injector);
}
// Save injector
inputStreamLookup.setSocketInjector(address, injector);
}
private void cleanupHook(PlayerInjector injector) {
// Clean up as much as possible
try {
if (injector != null)
injector.cleanupAll();
} catch (Exception ex) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_HOOK_CLEANUP_FAILED).callerParam(injector).error(ex));
}
}
/**
* Invoke special routines for handling disconnect before a player is uninjected.
* @param player - player to process.
*/
@Override
public void handleDisconnect(Player player) {
PlayerInjector injector = getInjector(player);
if (injector != null) {
injector.handleDisconnect();
}
}
@Override
public void updatePlayer(Player player) {
SocketAddress address = player.getAddress();
// Ignore logged out players
if (address != null) {
SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
if (injector != null) {
injector.setUpdatedPlayer(player);
} else {
inputStreamLookup.setSocketInjector(player.getAddress(),
new BukkitSocketInjector(player));
}
}
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
@Override
public boolean uninjectPlayer(Player player) {
return uninjectPlayer(player, false);
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @param prepareNextHook - whether or not we need to fix any lingering hooks.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
private boolean uninjectPlayer(Player player, boolean prepareNextHook) {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.remove(player);
if (injector != null) {
injector.cleanupAll();
// Remove the "hooked" network manager in our instance as well
if (prepareNextHook && injector instanceof NetworkObjectInjector) {
try {
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
dummyInjector.initializePlayer(player);
dummyInjector.setNetworkManager(injector.getNetworkManager(), true);
} catch (IllegalAccessException e) {
// Let the user know
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REVERT_HOOK).error(e));
}
}
return true;
}
}
return false;
}
/**
* Unregisters a player by the given address.
* <p>
* If the server handler has been created before we've gotten a chance to unject the player,
* the method will try a workaround to remove the injected hook in the NetServerHandler.
*
* @param address - address of the player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
if (!hasClosed && address != null) {
SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
// Clean up
if (injector != null)
uninjectPlayer(injector.getPlayer(), true);
return true;
}
return false;
}
/**
* Send the given packet to the given receiver.
* @param receiver - the player receiver.
* @param packet - the packet to send.
* @param filters - whether or not to invoke the packet filters.
* @throws InvocationTargetException If an error occured during sending.
*/
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
SocketInjector injector = getInjector(receiver);
// Send the packet, or drop it completely
if (injector != null) {
injector.sendServerPacket(packet.getHandle(), marker, filters);
} else {
throw new PlayerLoggedOutException(String.format(
"Unable to send packet %s (%s): Player %s has logged out.",
packet.getType(), packet, receiver
));
}
}
/**
* Recieve a packet as if it were sent by the given player.
* @param player - the sender.
* @param mcPacket - the packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
@Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
PlayerInjector injector = getInjector(player);
// Process the given packet, or simply give up
if (injector != null)
injector.processPacket(mcPacket);
else
throw new PlayerLoggedOutException(String.format(
"Unable to receieve packet %s. Player %s has logged out.",
mcPacket, player
));
}
/**
* Retrieve the injector associated with this player.
* @param player - the player to find.
* @return The injector, or NULL if not found.
*/
private PlayerInjector getInjector(Player player) {
PlayerInjector injector = playerInjection.get(player);
if (injector == null) {
// Try getting it from the player itself
SocketAddress address = player.getAddress();
// Must have logged out - there's nothing we can do
if (address == null)
return null;
// Look that up without blocking
SocketInjector result = inputStreamLookup.peekSocketInjector(address);
// Ensure that it is non-null and a player injector
if (result instanceof PlayerInjector)
return (PlayerInjector) result;
else
// Make a dummy injector them
return createDummyInjector(player);
} else {
return injector;
}
}
/**
* Construct a simple dummy injector incase none has been constructed.
* @param player - the CraftPlayer to construct for.
* @return A dummy injector, or NULL if the given player is not a CraftPlayer.
*/
private PlayerInjector createDummyInjector(Player player) {
if (!MinecraftReflection.getCraftPlayerClass().isAssignableFrom(player.getClass())) {
// No - this is not safe
return null;
}
try {
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
dummyInjector.initializePlayer(player);
// This probably means the player has disconnected
if (dummyInjector.getSocket() == null) {
return null;
}
inputStreamLookup.setSocketInjector(dummyInjector.getAddress(), dummyInjector);
dummyInjectors.put(player, dummyInjector);
return dummyInjector;
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access fields.", e);
}
}
/**
* Retrieve a player injector by looking for its NetworkManager.
* @param networkManager - current network manager.
* @return Related player injector.
*/
PlayerInjector getInjectorByNetworkHandler(Object networkManager) {
// That's not legal
if (networkManager == null)
return null;
// O(n) is okay in this instance. This is only a backup solution.
for (PlayerInjector injector : playerInjection.values()) {
if (injector.getNetworkManager() == networkManager)
return injector;
}
// None found
return null;
}
@Override
public boolean canRecievePackets() {
return false;
}
@Override
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
throw new UnsupportedOperationException("Proxy injection cannot handle received packets.");
}
/**
* Determine if the given listeners are valid.
* @param listeners - listeners to check.
*/
@Override
public void checkListener(Set<PacketListener> listeners) {
// Make sure the current listeners are compatible
if (getLastSuccessfulHook() != null) {
for (PacketListener listener : listeners) {
checkListener(listener);
}
}
}
/**
* Retrieve the last successful hook.
* <p>
* May be NULL if the hook has been uninjected.
* @return Last successful hook.
*/
private PlayerInjector getLastSuccessfulHook() {
return lastSuccessfulHook != null ? lastSuccessfulHook.get() : null;
}
/**
* Determine if a listener is valid or not.
* <p>
* If not, a warning will be printed to the console.
* @param listener - listener to check.
*/
@Override
public void checkListener(PacketListener listener) {
PlayerInjector last = getLastSuccessfulHook();
if (last != null) {
UnsupportedListener result = last.checkListener(version, listener);
// We won't prevent the listener, as it may still have valid packets
if (result != null) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNSUPPPORTED_LISTENER).messageParam(PacketAdapter.getPluginName(listener), result)
);
// These are illegal
for (int packetID : result.getPackets()) {
removePacketHandler(PacketType.findLegacy(packetID, Sender.CLIENT));
removePacketHandler(PacketType.findLegacy(packetID, Sender.SERVER));
}
}
}
}
/**
* Retrieve the current list of registered sending listeners.
* @return List of the sending listeners's packet IDs.
*/
@Override
public Set<PacketType> getSendingFilters() {
return PacketRegistry.toPacketTypes(sendingFilters.toSet(), Sender.SERVER);
}
@Override
public void close() {
// Guard
if (hasClosed || playerInjection == null)
return;
// Remove everything
for (PlayerInjector injection : playerInjection.values()) {
if (injection != null) {
injection.cleanupAll();
}
}
// Remove server handler
if (inputStreamLookup != null)
inputStreamLookup.cleanupAll();
if (serverInjection != null)
serverInjection.cleanupAll();
if (netLoginInjector != null)
netLoginInjector.cleanupAll();
inputStreamLookup = null;
serverInjection = null;
netLoginInjector = null;
hasClosed = true;
playerInjection.clear();
invoker = null;
}
@Override
public Channel getChannel(Player player) {
throw new UnsupportedOperationException();
}
}
@@ -1,370 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import com.google.common.base.Objects;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
/**
* Represents an array list that wraps another list, while automatically replacing one element with another.
* <p>
* The replaced elements can be recovered.
*
* @author Kristian
* @param <TKey> - type of the elements we're replacing.
*/
class ReplacedArrayList<TKey> extends ArrayList<TKey> {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 1008492765999744804L;
private BiMap<TKey, TKey> replaceMap = HashBiMap.create();
private List<TKey> underlyingList;
public ReplacedArrayList(List<TKey> underlyingList) {
this.underlyingList = underlyingList;
}
/**
* Invoked when a element inserted is replaced.
* @param inserting - the element inserted.
* @param replacement - the element that it should replace.
*/
protected void onReplacing(TKey inserting, TKey replacement) {
// Default is to do nothing.
}
/**
* Invoked when an element is being inserted.
* <p>
* This should be used to add a "replace" map.
* @param inserting - the element to insert.
*/
protected void onInserting(TKey inserting) {
// Default is again nothing
}
/**
* Invoked when an element is being removed.
* @param removing - the element being removed.
*/
protected void onRemoved(TKey removing) {
// Do nothing
}
@Override
public boolean add(TKey element) {
onInserting(element);
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
return delegate().add(replacement);
} else {
return delegate().add(element);
}
}
@Override
public void add(int index, TKey element) {
onInserting(element);
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
delegate().add(index, replacement);
} else {
delegate().add(index, element);
}
}
@Override
public boolean addAll(Collection<? extends TKey> collection) {
int oldSize = size();
for (TKey element : collection)
add(element);
return size() != oldSize;
}
@Override
public boolean addAll(int index, Collection<? extends TKey> elements) {
int oldSize = size();
for (TKey element : elements)
add(index++, element);
return size() != oldSize;
}
@SuppressWarnings("unchecked")
@Override
public boolean remove(Object object) {
boolean success = delegate().remove(object);
if (success)
onRemoved((TKey) object);
return success;
}
@Override
public TKey remove(int index) {
TKey removed = delegate().remove(index);
if (removed != null)
onRemoved(removed);
return removed;
}
@Override
public boolean removeAll(Collection<?> collection) {
int oldSize = size();
// Use the remove method
for (Object element : collection)
remove(element);
return size() != oldSize;
}
protected List<TKey> delegate() {
return underlyingList;
}
@Override
public void clear() {
for (TKey element : delegate())
onRemoved(element);
delegate().clear();
}
@Override
public boolean contains(Object o) {
return delegate().contains(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return delegate().containsAll(c);
}
@Override
public TKey get(int index) {
return delegate().get(index);
}
@Override
public int indexOf(Object o) {
return delegate().indexOf(o);
}
@Override
public boolean isEmpty() {
return delegate().isEmpty();
}
@Override
public Iterator<TKey> iterator() {
return delegate().iterator();
}
@Override
public int lastIndexOf(Object o) {
return delegate().lastIndexOf(o);
}
@Override
public ListIterator<TKey> listIterator() {
return delegate().listIterator();
}
@Override
public ListIterator<TKey> listIterator(int index) {
return delegate().listIterator(index);
}
@Override
public boolean retainAll(Collection<?> c) {
int oldSize = size();
for (Iterator<TKey> it = delegate().iterator(); it.hasNext(); ) {
TKey current = it.next();
// Remove elements that are not in the list
if (!c.contains(current)) {
it.remove();
onRemoved(current);
}
}
return size() != oldSize;
}
@Override
public TKey set(int index, TKey element) {
// Make sure to replace the element
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
return delegate().set(index, replacement);
} else {
return delegate().set(index, element);
}
}
@Override
public int size() {
return delegate().size();
}
@Override
public List<TKey> subList(int fromIndex, int toIndex) {
return delegate().subList(fromIndex, toIndex);
}
@Override
public Object[] toArray() {
return delegate().toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return delegate().toArray(a);
}
/**
* Add a replace rule.
* <p>
* This automatically replaces every existing element.
* @param target - instance to find.
* @param replacement - instance to replace with.
*/
public synchronized void addMapping(TKey target, TKey replacement) {
addMapping(target, replacement, false);
}
/**
* Retrieve the old value, if it exists.
* @param target - the key.
* @return The value that was replaced, or NULL.
*/
public TKey getMapping(TKey target) {
return replaceMap.get(target);
}
/**
* Add a replace rule.
* <p>
* This automatically replaces every existing element.
* @param target - instance to find.
* @param replacement - instance to replace with.
* @param ignoreExisting - whether or not to ignore the existing elements.
*/
public synchronized void addMapping(TKey target, TKey replacement, boolean ignoreExisting) {
replaceMap.put(target, replacement);
// Replace existing elements
if (!ignoreExisting) {
replaceAll(target, replacement);
}
}
/**
* Revert the given mapping.
* @param target - the instance we replaced.
* @return The old mapped value, or NULL if nothing was replaced.
*/
public synchronized TKey removeMapping(TKey target) {
// Make sure the mapping exist
if (replaceMap.containsKey(target)) {
TKey replacement = replaceMap.get(target);
replaceMap.remove(target);
// Revert existing elements
replaceAll(replacement, target);
return replacement;
}
return null;
}
/**
* Swap the new replaced value with its old value.
* @param target - the instance we replaced.
* @param The old mapped value, or NULL if nothing was swapped.
*/
public synchronized TKey swapMapping(TKey target) {
// Make sure the mapping exist
TKey replacement = removeMapping(target);
// Add the reverse
if (replacement != null) {
replaceMap.put(replacement, target);
}
return replacement;
}
/**
* Replace all instances of the given object.
* @param find - object to find.
* @param replace - object to replace it with.
*/
public synchronized void replaceAll(TKey find, TKey replace) {
for (int i = 0; i < underlyingList.size(); i++) {
if (Objects.equal(underlyingList.get(i), find)) {
onReplacing(find, replace);
underlyingList.set(i, replace);
}
}
}
/**
* Undo all replacements.
*/
public synchronized void revertAll() {
// No need to do anything else
if (replaceMap.size() < 1)
return;
BiMap<TKey, TKey> inverse = replaceMap.inverse();
for (int i = 0; i < underlyingList.size(); i++) {
TKey replaced = underlyingList.get(i);
if (inverse.containsKey(replaced)) {
TKey original = inverse.get(replaced);
onReplacing(replaced, original);
underlyingList.set(i, original);
}
}
replaceMap.clear();
}
@Override
protected void finalize() throws Throwable {
revertAll();
super.finalize();
}
}
@@ -1,64 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.util.Arrays;
import com.google.common.base.Joiner;
/**
* Represents an error message from a player injector.
*
* @author Kristian
*/
class UnsupportedListener {
private String message;
private int[] packets;
/**
* Create a new error message.
* @param message - the message.
* @param packets - unsupported packets.
*/
public UnsupportedListener(String message, int[] packets) {
super();
this.message = message;
this.packets = packets;
}
/**
* Retrieve the error message.
* @return Error message.
*/
public String getMessage() {
return message;
}
/**
* Retrieve all unsupported packets.
* @return Unsupported packets.
*/
public int[] getPackets() {
return packets;
}
@Override
public String toString() {
return String.format("%s (%s)", message, Joiner.on(", ").join(Arrays.asList(packets)));
}
}
@@ -1,45 +0,0 @@
package com.comphenix.protocol.injector.spigot;
import java.util.Set;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.Sets;
/**
* Dummy packet injector that simply delegates to its parent Spigot packet injector or receiving filters.
*
* @author Kristian
*/
class DummyPacketInjector extends AbstractPacketInjector {
private SpigotPacketInjector injector;
private PacketTypeSet lastBufferedPackets = new PacketTypeSet();
public DummyPacketInjector(SpigotPacketInjector injector, PacketTypeSet reveivedFilters) {
super(reveivedFilters);
this.injector = injector;
}
@Override
public void inputBuffersChanged(Set<PacketType> set) {
Set<PacketType> removed = Sets.difference(lastBufferedPackets.values(), set);
Set<PacketType> added = Sets.difference(set, lastBufferedPackets.values());
// Update the proxy packet injector
for (PacketType packet : removed) {
injector.getProxyPacketInjector().removePacketHandler(packet);
}
for (PacketType packet : added) {
injector.getProxyPacketInjector().addPacketHandler(packet, null);
}
}
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
return injector.packetReceived(packet, client, buffered);
}
}
@@ -1,92 +0,0 @@
package com.comphenix.protocol.injector.spigot;
import io.netty.channel.Channel;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
/**
* Dummy player handler that simply delegates to its parent Spigot packet injector.
*
* @author Kristian
*/
class DummyPlayerHandler extends AbstractPlayerHandler {
private SpigotPacketInjector injector;
@Override
public int getProtocolVersion(Player player) {
// Just use the server version
return MinecraftProtocolVersion.getCurrentVersion();
}
public DummyPlayerHandler(SpigotPacketInjector injector, PacketTypeSet sendingFilters) {
super(sendingFilters);
this.injector = injector;
}
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
return true;
}
@Override
public boolean uninjectPlayer(Player player) {
injector.uninjectPlayer(player);
return true;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
injector.sendServerPacket(receiver, packet, marker, filters);
}
@Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
injector.processPacket(player, mcPacket);
}
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
// We don't care about strategy
injector.injectPlayer(player);
}
@Override
public boolean hasMainThreadListener(PacketType type) {
return sendingFilters.contains(type);
}
@Override
public void handleDisconnect(Player player) {
// Just ignore
}
@Override
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
// Associate this buffered data
if (buffered != null) {
injector.saveBuffered(packet.getHandle(), buffered);
}
return null;
}
@Override
public void updatePlayer(Player player) {
// Do nothing
}
@Override
public Channel getChannel(Player player) {
throw new UnsupportedOperationException();
}
}
@@ -1,672 +0,0 @@
package com.comphenix.protocol.injector.spigot;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.DelegatedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.packet.LegacyNetworkMarker;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.player.NetworkObjectInjector;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
/**
* Offload all the work to Spigot, if possible.
*
* @author Kristian
*/
public class SpigotPacketInjector implements SpigotPacketListener {
public static final ReportType REPORT_CANNOT_CLEANUP_SPIGOT = new ReportType("Cannot cleanup Spigot listener.");
// Lazily retrieve the spigot listener class
private static volatile Class<?> spigotListenerClass;
private static volatile boolean classChecked;
// Retrieve the entity player from a PlayerConnection
private static volatile Field playerConnectionPlayer;
// Packets that are not to be processed by the filters
private Set<Object> ignoredPackets = Collections.newSetFromMap(new MapMaker().weakKeys().<Object, Boolean>makeMap());
/**
* The amount of ticks to wait before removing all traces of a player.
*/
private static final int CLEANUP_DELAY = 100;
// The listener we will register on Spigot.
// Unfortunately, due to the use of PlayerConnection, INetworkManager and Packet, we're
// unable to reference it directly. But with CGLib, it shouldn't cost us much.
private Object dynamicListener;
// Reference to ProtocolLib
private Plugin plugin;
// Different sending filters
private PacketTypeSet queuedFilters;
private PacketTypeSet reveivedFilters;
// NetworkManager to injector and player
private ConcurrentMap<Object, NetworkObjectInjector> networkManagerInjector = Maps.newConcurrentMap();
// Player to injector
private ConcurrentMap<Player, NetworkObjectInjector> playerInjector = Maps.newConcurrentMap();
// For handling read buffered packet data
private Map<Object, byte[]> readBufferedPackets = new MapMaker().weakKeys().makeMap();
// Responsible for informing the PL packet listeners
private ListenerInvoker invoker;
private ErrorReporter reporter;
private Server server;
// The proxy packet injector
private PacketInjector proxyPacketInjector;
// Background task
private static final int BACKGROUND_DELAY = 30 * PacketFilterManager.TICKS_PER_SECOND;
private int backgroundId;
/**
* Create a new spigot injector.
* @param reporter - error reporter
* @param invoker - listener invoker
* @param server - server
*/
public SpigotPacketInjector(ErrorReporter reporter, ListenerInvoker invoker, Server server) {
this.reporter = reporter;
this.invoker = invoker;
this.server = server;
this.queuedFilters = new PacketTypeSet();
this.reveivedFilters = new PacketTypeSet();
}
/**
* Retrieve the underlying listener invoker.
* @return The invoker.
*/
public ListenerInvoker getInvoker() {
return invoker;
}
/**
* Set the real proxy packet injector.
* @param proxyPacketInjector - the real injector.
*/
public void setProxyPacketInjector(PacketInjector proxyPacketInjector) {
this.proxyPacketInjector = proxyPacketInjector;
}
/**
* Retrieve the real proxy packet injector.
* @return The real injector.
*/
public PacketInjector getProxyPacketInjector() {
return proxyPacketInjector;
}
/**
* Retrieve the spigot packet listener class.
* @return The listener class.
*/
private static Class<?> getSpigotListenerClass() {
if (!classChecked) {
try {
spigotListenerClass = SpigotPacketInjector.class.getClassLoader().loadClass("org.spigotmc.netty.PacketListener");
} catch (ClassNotFoundException e) {
return null;
} finally {
// We've given it a try now
classChecked = true;
}
}
return spigotListenerClass;
}
/**
* Retrieve the register packet listener method.
* @return The method used to register a packet listener.
*/
private static Method getRegisterMethod() {
Class<?> clazz = getSpigotListenerClass();
if (clazz != null) {
try {
return clazz.getMethod("register", clazz, Plugin.class);
} catch (SecurityException e) {
// If this happens, then ... we're doomed
throw new RuntimeException("Reflection is not allowed.", e);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Cannot find register() method in " + clazz, e);
}
}
// Also bad
throw new IllegalStateException("Spigot could not be found!");
}
/**
* Determine if there is a Spigot packet listener.
* @return Spigot packet listener.
*/
public static boolean canUseSpigotListener() {
return getSpigotListenerClass() != null;
}
/**
* Register the Spigot packet injector.
* @param plugin - the parent plugin.
* @return TRUE if we registered the plugin, FALSE otherwise.
*/
public boolean register(Plugin plugin) {
if (hasRegistered())
return false;
// Save the plugin too
this.plugin = plugin;
final Callback[] callbacks = new Callback[3];
final boolean[] found = new boolean[3];
// Packets received from the clients
callbacks[0] = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return SpigotPacketInjector.this.packetReceived(args[0], args[1], args[2]);
}
};
// Packet sent/queued
callbacks[1] = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return SpigotPacketInjector.this.packetQueued(args[0], args[1], args[2]);
}
};
// Don't care for everything else
callbacks[2] = NoOp.INSTANCE;
Enhancer enhancer = EnhancerFactory.getInstance().createEnhancer();
enhancer.setSuperclass(getSpigotListenerClass());
enhancer.setCallbacks(callbacks);
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
// We'll be pretty stringent
if (matchMethod("packetReceived", method)) {
found[0] = true;
return 0;
} else if (matchMethod("packetQueued", method)) {
found[1] = true;
return 1;
} else {
found[2] = true;
return 2;
}
}
});
dynamicListener = enhancer.create();
// Verify methods
if (!found[0])
throw new IllegalStateException("Unable to find a valid packet receiver in Spigot.");
if (!found[1])
throw new IllegalStateException("Unable to find a valid packet queue in Spigot.");
// Lets register it too
try {
getRegisterMethod().invoke(null, dynamicListener, plugin);
} catch (Exception e) {
throw new RuntimeException("Cannot register Spigot packet listener.", e);
}
// Remember to register background task
backgroundId = createBackgroundTask();
// If we succeed
return true;
}
/**
* Create and register a background task.
* @return The background task ID.
*/
private int createBackgroundTask() {
return Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new Runnable() {
@Override
public void run() {
cleanupInjectors();
}
}, BACKGROUND_DELAY, BACKGROUND_DELAY);
}
/**
* Ensure that all disconnected injectors are removed from memory.
*/
private void cleanupInjectors() {
for (NetworkObjectInjector injector : networkManagerInjector.values()) {
try {
if (injector.getSocket() != null && injector.getSocket().isClosed()) {
cleanupInjector(injector);
}
} catch (Exception e) {
reporter.reportMinimal(plugin, "cleanupInjectors", e);
// What?
cleanupInjector(injector);
}
}
}
/**
* Remove a given network object injector.
* @param injector - the injector.
*/
private void cleanupInjector(final NetworkObjectInjector injector) {
// Clean up
playerInjector.remove(injector.getPlayer());
playerInjector.remove(injector.getUpdatedPlayer());
networkManagerInjector.remove(injector.getNetworkManager());
}
/**
* Determine if the given method is a valid packet receiver or queued method.
* @param methodName - the expected name of the method.
* @param method - the method we're testing.
* @return TRUE if this is a correct method, FALSE otherwise.
*/
private boolean matchMethod(String methodName, Method method) {
return FuzzyMethodContract.newBuilder().
nameExact(methodName).
parameterCount(3).
parameterSuperOf(MinecraftReflection.getNetHandlerClass(), 1).
parameterSuperOf(MinecraftReflection.getPacketClass(), 2).
returnTypeExact(MinecraftReflection.getPacketClass()).
build().
isMatch(MethodInfo.fromMethod(method), null);
}
/**
* Determine if the Spigot packet listener has been registered.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean hasRegistered() {
return dynamicListener != null;
}
/**
* Retrieve the dummy player injection handler.
* @return Dummy player injection handler.
*/
public PlayerInjectionHandler getPlayerHandler() {
return new DummyPlayerHandler(this, queuedFilters);
}
/**
* Retrieve the dummy packet injection handler.
* @return Dummy packet injection handler.
*/
public PacketInjector getPacketInjector() {
return new DummyPacketInjector(this, reveivedFilters);
}
/**
* Retrieve the currently registered injector for the given player.
* @param player - injected player.
* @param createNew - whether or not to create a new injector if the current is missing.
* @return The injector.
*/
NetworkObjectInjector getInjector(Player player, boolean createNew) {
NetworkObjectInjector injector = playerInjector.get(player);
if (injector == null && createNew) {
// Check for temporary players ..
if ((player instanceof Factory))
throw new IllegalArgumentException("Cannot inject tempoary player " + player);
try {
NetworkObjectInjector created = new NetworkObjectInjector(
filterImpossibleWarnings(reporter), null, invoker, null);
created.initializePlayer(player);
if (created.getNetworkManager() == null)
throw new PlayerLoggedOutException("Player " + player + " has logged out.");
injector = saveInjector(created.getNetworkManager(), created);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create dummy injector.", e);
}
}
return injector;
}
/**
* Retrieve or create a registered injector for the given network manager and connection.
* @param networkManager - a INetworkManager object.
* @param connection - a Connection (PlayerConnection, PendingConnection) object.
* @return The created NetworkObjectInjector with a temporary player.
*/
NetworkObjectInjector getInjector(Object networkManager, Object connection) {
NetworkObjectInjector dummyInjector = networkManagerInjector.get(networkManager);
if (dummyInjector == null) {
// Inject the network manager
try {
NetworkObjectInjector created = new NetworkObjectInjector(
filterImpossibleWarnings(reporter), null, invoker, null);
if (MinecraftReflection.isLoginHandler(connection)) {
created.initialize(connection);
created.setPlayer(created.createTemporaryPlayer(server));
} else if (MinecraftReflection.isServerHandler(connection)) {
// Get the player instead
if (playerConnectionPlayer == null)
playerConnectionPlayer = FuzzyReflection.fromObject(connection).
getFieldByType("player", MinecraftReflection.getEntityPlayerClass());
Object entityPlayer = playerConnectionPlayer.get(connection);
created.initialize(MinecraftReflection.getBukkitEntity(entityPlayer));
} else {
throw new IllegalArgumentException("Unregonized connection in NetworkManager.");
}
dummyInjector = saveInjector(networkManager, created);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create dummy injector.", e);
}
}
return dummyInjector;
}
/**
* Return a delegated error reporter that ignores certain warnings that are irrelevant on Spigot.
* @param reporter - error reporter to delegate.
* @return The filtered error reporter.
*/
private ErrorReporter filterImpossibleWarnings(ErrorReporter reporter) {
return new DelegatedErrorReporter(reporter) {
@Override
protected Report filterReport(Object sender, Report report, boolean detailed) {
// This doesn't matter - ignore it
if (report.getType() == NetworkObjectInjector.REPORT_DETECTED_CUSTOM_SERVER_HANDLER)
return null;
return report;
}
};
}
/**
* Save a given player injector for later.
* @param networkManager - the associated network manager.
* @param created - the created network object creator.
* @return Any other network injector that came before us.
*/
private NetworkObjectInjector saveInjector(Object networkManager, NetworkObjectInjector created) {
// Concurrency - use the same injector!
NetworkObjectInjector result = networkManagerInjector.putIfAbsent(networkManager, created);
if (result == null) {
result = created;
}
// Save the player as well
playerInjector.put(created.getPlayer(), created);
return result;
}
/**
* Save the buffered serialized input packet.
* @param handle - the associated packet.
* @param buffered - the buffere data to save.
*/
public void saveBuffered(Object handle, byte[] buffered) {
readBufferedPackets.put(handle, buffered);
}
@Override
public Object packetReceived(Object networkManager, Object connection, Object packet) {
if (reveivedFilters.contains(packet.getClass())) {
@SuppressWarnings("deprecation")
Integer id = invoker.getPacketID(packet);
// Check for ignored packets
if (ignoredPackets.remove(packet)) {
return packet;
}
Player sender = getInjector(networkManager, connection).getUpdatedPlayer();
PacketType type = PacketType.findLegacy(id, Sender.CLIENT);
PacketContainer container = new PacketContainer(type, packet);
PacketEvent event = packetReceived(container, sender, readBufferedPackets.get(packet));
if (!event.isCancelled())
return event.getPacket().getHandle();
else
return null; // Cancel
}
// Don't change anything
return packet;
}
@Override
public Object packetQueued(Object networkManager, Object connection, Object packet) {
if (queuedFilters.contains(packet.getClass())) {
@SuppressWarnings("deprecation")
Integer id = invoker.getPacketID(packet);
// Check for ignored packets
if (ignoredPackets.remove(packet)) {
return packet;
}
Player reciever = getInjector(networkManager, connection).getUpdatedPlayer();
PacketType type = PacketType.findLegacy(id, Sender.SERVER);
PacketContainer container = new PacketContainer(type, packet);
PacketEvent event = packetQueued(container, reciever);
if (!event.isCancelled())
return event.getPacket().getHandle();
else
return null; // Cancel
}
// Don't change anything
return packet;
}
/**
* Called to inform the event listeners of a queued packet.
* @param packet - the packet that is to be sent.
* @param receiver - the receiver of this packet.
* @return The packet event that was used.
*/
PacketEvent packetQueued(PacketContainer packet, Player receiver) {
PacketEvent event = PacketEvent.fromServer(this, packet, receiver);
invoker.invokePacketSending(event);
return event;
}
/**
* Called to inform the event listeners of a received packet.
* @param packet - the packet that has been receieved.
* @param sender - the client packet.
* @return The packet event that was used.
*/
PacketEvent packetReceived(PacketContainer packet, Player sender, byte[] buffered) {
NetworkMarker marker = buffered != null ? new LegacyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered, packet.getType()) : null;
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
invoker.invokePacketRecieving(event);
return event;
}
/**
* Called when a player has logged in properly.
* @param player - the player that has logged in.
*/
void injectPlayer(Player player) {
try {
NetworkObjectInjector dummy = new NetworkObjectInjector(
filterImpossibleWarnings(reporter), player, invoker, null);
dummy.initializePlayer(player);
// Save this player for the network manager
NetworkObjectInjector realInjector = networkManagerInjector.get(dummy.getNetworkManager());
if (realInjector != null) {
// Update all future references
realInjector.setUpdatedPlayer(player);
playerInjector.put(player, realInjector);
} else {
// Ah - in that case, save this injector
saveInjector(dummy.getNetworkManager(), dummy);
}
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot inject " + player);
}
}
/**
* Uninject the given player.
* @param player - the player to uninject.
*/
void uninjectPlayer(Player player) {
final NetworkObjectInjector injector = getInjector(player, false);
if (player != null && injector != null) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
@Override
public void run() {
cleanupInjector(injector);
}
}, CLEANUP_DELAY);
}
}
/**
* Invoked when a plugin wants to sent a packet.
* @param receiver - the packet receiver.
* @param packet - the packet to transmit.
* @param marker - the network marker object.
* @param filters - whether or not to invoke the packet listeners.
* @throws InvocationTargetException If anything went wrong.
*/
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
NetworkObjectInjector networkObject = getInjector(receiver, true);
// If TRUE, process this packet like any other
if (filters)
ignoredPackets.remove(packet.getHandle());
else
ignoredPackets.add(packet.getHandle());
networkObject.sendServerPacket(packet.getHandle(), marker, filters);
}
/**
* Invoked when a plugin wants to simulate receiving a packet.
* @param player - the supposed sender.
* @param mcPacket - the packet to receieve.
* @throws IllegalAccessException Reflection is not permitted.
* @throws InvocationTargetException Minecraft threw an exception.
*/
void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
NetworkObjectInjector networkObject = getInjector(player, true);
// We will always ignore this packet
ignoredPackets.add(mcPacket);
networkObject.processPacket(mcPacket);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void cleanupListener() {
Class<?> listenerClass = getSpigotListenerClass();
// Yeah ... there's no easy way to remove the listener
synchronized (listenerClass) {
try {
Field listenersField = FieldUtils.getField(listenerClass, "listeners", true);
Field bakedField = FieldUtils.getField(listenerClass, "baked", true);
Map<Object, Plugin> listenerMap = (Map<Object, Plugin>) listenersField.get(null);
List<Object> listenerArray = Lists.newArrayList((Object[]) bakedField.get(null));
listenerMap.remove(dynamicListener);
listenerArray.remove(dynamicListener);
// Save the array back
bakedField.set(null, Iterables.toArray(listenerArray, (Class)listenerClass));
// Success
dynamicListener = null;
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_CLEANUP_SPIGOT).
callerParam(dynamicListener).error(e));
}
}
}
/**
* Invoked when the server is cleaning up.
*/
public void cleanupAll() {
// Cleanup the Spigot listener
if (dynamicListener != null) {
cleanupListener();
}
if (backgroundId >= 0) {
Bukkit.getScheduler().cancelTask(backgroundId);
backgroundId = -1;
}
// Cleanup network marker
if (proxyPacketInjector != null) {
proxyPacketInjector.cleanupAll();
}
}
}
@@ -1,34 +0,0 @@
package com.comphenix.protocol.injector.spigot;
/**
* Represents a proxy for a Spigot packet listener.
*
* @author Kristian
*/
interface SpigotPacketListener {
/**
* Called when a packet has been received and is about to be handled by the
* current Connection.
* <p>
* The returned packet will be the packet passed on for handling, or in the case of
* null being returned, not handled at all.
*
* @param networkManager - the NetworkManager receiving the packet
* @param connection - the connection which will handle the packet
* @param packet - the received packet
* @return the packet to be handled, or null to cancel
*/
public Object packetReceived(Object networkManager, Object connection, Object packet);
/**
* Called when a packet is queued to be sent.
* <p>
* The returned packet will be the packet sent. In the case of null being returned,
* the packet will not be sent.
* @param networkManager - the NetworkManager which will send the packet
* @param connection - the connection which queued the packet
* @param packet - the queue packet
* @return the packet to be sent, or null if the packet will not be sent.
*/
public Object packetQueued(Object networkManager, Object connection, Object packet);
}
@@ -1,753 +0,0 @@
/*
* Copyright 2011-2013 Tyler Blair. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and contributors and should not be interpreted as representing official policies,
* either expressed or implied, of anybody else.
*/
package com.comphenix.protocol.metrics;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.zip.GZIPOutputStream;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import com.comphenix.protocol.utility.Util;
import com.comphenix.protocol.utility.WrappedScheduler;
public class Metrics {
/**
* The current revision number
*/
private final static int REVISION = 7;
/**
* The base url of the metrics domain
*/
private static final String BASE_URL = "http://report.mcstats.org";
/**
* The url used to report a server's status
*/
private static final String REPORT_URL = "/plugin/%s";
/**
* Interval of time to ping (in minutes)
*/
private static final int PING_INTERVAL = 15;
/**
* The plugin this metrics submits for
*/
private final Plugin plugin;
/**
* All of the custom graphs to submit to metrics
*/
private final Set<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
/**
* The plugin configuration file
*/
private final YamlConfiguration configuration;
/**
* The plugin configuration file
*/
private final File configurationFile;
/**
* Unique server id
*/
private final String guid;
/**
* Debug mode
*/
private final boolean debug;
/**
* Lock for synchronization
*/
private final Object optOutLock = new Object();
/**
* The scheduled task
*/
private volatile WrappedScheduler.TaskWrapper task = null;
public Metrics(final Plugin plugin) throws IOException {
if (plugin == null) {
throw new IllegalArgumentException("Plugin cannot be null");
}
this.plugin = plugin;
// load the config
configurationFile = getConfigFile();
configuration = YamlConfiguration.loadConfiguration(configurationFile);
// add some defaults
configuration.addDefault("opt-out", false);
configuration.addDefault("guid", UUID.randomUUID().toString());
configuration.addDefault("debug", false);
// Do we need to create the file?
if (configuration.get("guid", null) == null) {
configuration.options().header("http://mcstats.org").copyDefaults(true);
configuration.save(configurationFile);
}
// Load the guid then
guid = configuration.getString("guid");
debug = configuration.getBoolean("debug", false);
}
/**
* Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics
* website. Plotters can be added to the graph object returned.
*
* @param name The name of the graph
* @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given
*/
public Graph createGraph(final String name) {
if (name == null) {
throw new IllegalArgumentException("Graph name cannot be null");
}
// Construct the graph object
final Graph graph = new Graph(name);
// Now we can add our graph
graphs.add(graph);
// and return back
return graph;
}
/**
* Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend
*
* @param graph The name of the graph
*/
public void addGraph(final Graph graph) {
if (graph == null) {
throw new IllegalArgumentException("Graph cannot be null");
}
graphs.add(graph);
}
/**
* Start measuring statistics. This will immediately create an async repeating task as the plugin and send the
* initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200
* ticks.
*
* @return True if statistics measuring is running, otherwise false.
*/
public boolean start() {
synchronized (optOutLock) {
// Did we opt out?
if (isOptOut()) {
return false;
}
// Is metrics already running?
if (task != null) {
return true;
}
// Begin hitting the server with glorious data
task = WrappedScheduler.runAsynchronouslyRepeat(plugin, new Runnable() {
private boolean firstPost = true;
@Override
public void run() {
try {
// This has to be synchronized or it can collide with the disable method.
synchronized (optOutLock) {
// Disable Task, if it is running and the server owner decided to opt-out
if (isOptOut() && task != null) {
task.cancel();
task = null;
// Tell all plotters to stop gathering information.
for (Graph graph : graphs) {
graph.onOptOut();
}
}
}
// We use the inverse of firstPost because if it is the first time we are posting,
// it is not a interval ping, so it evaluates to FALSE
// Each time thereafter it will evaluate to TRUE, i.e PING!
postPlugin(!firstPost);
// After the first post we set firstPost to false
// Each post thereafter will be a ping
firstPost = false;
} catch (IOException e) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage());
}
}
}
}, 0, PING_INTERVAL * 1200);
return true;
}
}
/**
* Has the server owner denied plugin metrics?
*
* @return true if metrics should be opted out of it
*/
public boolean isOptOut() {
synchronized (optOutLock) {
try {
// Reload the metrics file
configuration.load(getConfigFile());
} catch (IOException ex) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
} catch (InvalidConfigurationException ex) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
}
return configuration.getBoolean("opt-out", false);
}
}
/**
* Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task.
*
* @throws IOException If configuration saving fails
*/
public void enable() throws IOException {
// This has to be synchronized or it can collide with the check in the task.
synchronized (optOutLock) {
// Check if the server owner has already set opt-out, if not, set it.
if (isOptOut()) {
configuration.set("opt-out", false);
configuration.save(configurationFile);
}
// Enable Task, if it is not running
if (task == null) {
start();
}
}
}
/**
* Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task.
*
* @throws IOException If configuration saving fails
*/
public void disable() throws IOException {
// This has to be synchronized or it can collide with the check in the task.
synchronized (optOutLock) {
// Check if the server owner has already set opt-out, if not, set it.
if (!isOptOut()) {
configuration.set("opt-out", true);
configuration.save(configurationFile);
}
// Disable Task, if it is running
if (task != null) {
task.cancel();
task = null;
}
}
}
/**
* Gets the File object of the config file that should be used to store data such as the GUID and opt-out status
*
* @return the File object for the config file
*/
public File getConfigFile() {
// I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use
// is to abuse the plugin object we already have
// plugin.getDataFolder() => base/plugins/PluginA/
// pluginsFolder => base/plugins/
// The base is not necessarily relative to the startup directory.
File pluginsFolder = plugin.getDataFolder().getParentFile();
// return => base/plugins/PluginMetrics/config.yml
return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml");
}
/**
* Generic method that posts a plugin to the metrics website
*
* @throws IOException If posting fails
*/
private void postPlugin(final boolean isPing) throws IOException {
// Server software specific section
PluginDescriptionFile description = plugin.getDescription();
String pluginName = description.getName();
boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled
String pluginVersion = trimVersion(description.getVersion()); // ProtocolLib
String serverVersion = Bukkit.getVersion();
int playersOnline = Util.getOnlinePlayers().size(); // ProtocolLib
// END server software specific section -- all code below does not use any code outside of this class / Java
// Construct the post data
StringBuilder json = new StringBuilder(1024);
json.append('{');
// The plugin's description file containg all of the plugin data such as name, version, author, etc
appendJSONPair(json, "guid", guid);
appendJSONPair(json, "plugin_version", pluginVersion);
appendJSONPair(json, "server_version", serverVersion);
appendJSONPair(json, "players_online", Integer.toString(playersOnline));
// New data as of R6
String osname = System.getProperty("os.name");
String osarch = System.getProperty("os.arch");
String osversion = System.getProperty("os.version");
String java_version = System.getProperty("java.version");
int coreCount = Runtime.getRuntime().availableProcessors();
// normalize os arch .. amd64 -> x86_64
if (osarch.equals("amd64")) {
osarch = "x86_64";
}
appendJSONPair(json, "osname", osname);
appendJSONPair(json, "osarch", osarch);
appendJSONPair(json, "osversion", osversion);
appendJSONPair(json, "cores", Integer.toString(coreCount));
appendJSONPair(json, "auth_mode", onlineMode ? "1" : "0");
appendJSONPair(json, "java_version", java_version);
// If we're pinging, append it
if (isPing) {
appendJSONPair(json, "ping", "1");
}
if (graphs.size() > 0) {
synchronized (graphs) {
json.append(',');
json.append('"');
json.append("graphs");
json.append('"');
json.append(':');
json.append('{');
boolean firstGraph = true;
final Iterator<Graph> iter = graphs.iterator();
while (iter.hasNext()) {
Graph graph = iter.next();
StringBuilder graphJson = new StringBuilder();
graphJson.append('{');
for (Plotter plotter : graph.getPlotters()) {
appendJSONPair(graphJson, plotter.getColumnName(), Integer.toString(plotter.getValue()));
}
graphJson.append('}');
if (!firstGraph) {
json.append(',');
}
json.append(escapeJSON(graph.getName()));
json.append(':');
json.append(graphJson);
firstGraph = false;
}
json.append('}');
}
}
// close json
json.append('}');
// Create the url
URL url = new URL(BASE_URL + String.format(REPORT_URL, urlEncode(pluginName)));
// Connect to the website
URLConnection connection;
// Mineshafter creates a socks proxy, so we can safely bypass it
// It does not reroute POST requests so we need to go around it
if (isMineshafterPresent()) {
connection = url.openConnection(Proxy.NO_PROXY);
} else {
connection = url.openConnection();
}
byte[] uncompressed = json.toString().getBytes();
byte[] compressed = gzip(json.toString());
// Headers
connection.addRequestProperty("User-Agent", "MCStats/" + REVISION);
connection.addRequestProperty("Content-Type", "application/json");
connection.addRequestProperty("Content-Encoding", "gzip");
connection.addRequestProperty("Content-Length", Integer.toString(compressed.length));
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.setDoOutput(true);
if (debug) {
System.out.println("[Metrics] Prepared request for " + pluginName + " uncompressed=" + uncompressed.length + " compressed=" + compressed.length);
}
// Write the data
OutputStream os = connection.getOutputStream();
os.write(compressed);
os.flush();
// Now read the response
final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String response = reader.readLine();
// close resources
os.close();
reader.close();
if (response == null || response.startsWith("ERR") || response.startsWith("7")) {
if (response == null) {
response = "null";
} else if (response.startsWith("7")) {
response = response.substring(response.startsWith("7,") ? 2 : 1);
}
throw new IOException(response);
} else {
// Is this the first update this hour?
if (response.equals("1") || response.contains("This is your first update this hour")) {
synchronized (graphs) {
final Iterator<Graph> iter = graphs.iterator();
while (iter.hasNext()) {
final Graph graph = iter.next();
for (Plotter plotter : graph.getPlotters()) {
plotter.reset();
}
}
}
}
}
}
// Trim the version string to not include the build number
// Not doing this has an interesting effect on the version graph
private String trimVersion(String version) {
return version.contains("-b") ? version.substring(0, version.indexOf("-b")) : version;
}
/**
* GZip compress a string of bytes
*
* @param input Input to compress
* @return Compressed string
*/
public static byte[] gzip(String input) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = null;
try {
gzos = new GZIPOutputStream(baos);
gzos.write(input.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (gzos != null) try {
gzos.close();
} catch (IOException ignore) {
}
}
return baos.toByteArray();
}
/**
* Check if mineshafter is present. If it is, we need to bypass it to send POST requests
*
* @return true if mineshafter is installed on the server
*/
private boolean isMineshafterPresent() {
try {
Class.forName("mineshafter.MineServer");
return true;
} catch (Exception e) {
return false;
}
}
// Appends a json encoded key/value pair to the given string builder.
private static void appendJSONPair(StringBuilder json, String key, String value) {
boolean isValueNumeric = false;
try {
if (value.equals("0") || !value.endsWith("0")) {
Double.parseDouble(value);
isValueNumeric = true;
}
} catch (NumberFormatException e) {
isValueNumeric = false;
}
if (json.charAt(json.length() - 1) != '{') {
json.append(',');
}
json.append(escapeJSON(key));
json.append(':');
if (isValueNumeric) {
json.append(value);
} else {
json.append(escapeJSON(value));
}
}
// Escape a string to create a valid JSON string
private static String escapeJSON(String text) {
StringBuilder builder = new StringBuilder();
builder.append('"');
for (int index = 0; index < text.length(); index++) {
char chr = text.charAt(index);
switch (chr) {
case '"':
case '\\':
builder.append('\\');
builder.append(chr);
break;
case '\b':
builder.append("\\b");
break;
case '\t':
builder.append("\\t");
break;
case '\n':
builder.append("\\n");
break;
case '\r':
builder.append("\\r");
break;
default:
if (chr < ' ') {
String t = "000" + Integer.toHexString(chr);
builder.append("\\u" + t.substring(t.length() - 4));
} else {
builder.append(chr);
}
break;
}
}
builder.append('"');
return builder.toString();
}
// Encode text as UTF-8
private static String urlEncode(final String text) throws UnsupportedEncodingException {
return URLEncoder.encode(text, "UTF-8");
}
/**
* Represents a custom graph on the website
*/
public static class Graph {
/**
* The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is
* rejected
*/
private final String name;
/**
* The set of plotters that are contained within this graph
*/
private final Set<Plotter> plotters = new LinkedHashSet<Plotter>();
private Graph(final String name) {
this.name = name;
}
/**
* Gets the graph's name
*
* @return the Graph's name
*/
public String getName() {
return name;
}
/**
* Add a plotter to the graph, which will be used to plot entries
*
* @param plotter the plotter to add to the graph
*/
public void addPlotter(final Plotter plotter) {
plotters.add(plotter);
}
/**
* Remove a plotter from the graph
*
* @param plotter the plotter to remove from the graph
*/
public void removePlotter(final Plotter plotter) {
plotters.remove(plotter);
}
/**
* Gets an <b>unmodifiable</b> set of the plotter objects in the graph
*
* @return an unmodifiable {@link java.util.Set} of the plotter objects
*/
public Set<Plotter> getPlotters() {
return Collections.unmodifiableSet(plotters);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(final Object object) {
if (!(object instanceof Graph)) {
return false;
}
final Graph graph = (Graph) object;
return graph.name.equals(name);
}
/**
* Called when the server owner decides to opt-out of BukkitMetrics while the server is running.
*/
protected void onOptOut() {
}
}
/**
* Interface used to collect custom data for a plugin
*/
public static abstract class Plotter {
/**
* The plot's name
*/
private final String name;
/**
* Construct a plotter with the default plot name
*/
public Plotter() {
this("Default");
}
/**
* Construct a plotter with a specific plot name
*
* @param name the name of the plotter to use, which will show up on the website
*/
public Plotter(final String name) {
this.name = name;
}
/**
* Get the current value for the plotted point. Since this function defers to an external function it may or may
* not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called
* from any thread so care should be taken when accessing resources that need to be synchronized.
*
* @return the current value for the point to be plotted.
*/
public abstract int getValue();
/**
* Get the column name for the plotted point
*
* @return the plotted point's column name
*/
public String getColumnName() {
return name;
}
/**
* Called after the website graphs have been updated
*/
public void reset() {
}
@Override
public int hashCode() {
return getColumnName().hashCode();
}
@Override
public boolean equals(final Object object) {
if (!(object instanceof Plotter)) {
return false;
}
final Plotter plotter = (Plotter) object;
return plotter.name.equals(name) && plotter.getValue() == getValue();
}
}
}
@@ -1,82 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.metrics;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.metrics.Metrics.Graph;
public class Statistics {
// Metrics
private Metrics metrics;
public Statistics(Plugin plugin) throws IOException {
metrics = new Metrics(plugin);
// Determine who is using this library
addPluginUserGraph(metrics);
metrics.start();
}
private void addPluginUserGraph(Metrics metrics) {
Graph pluginUsers = metrics.createGraph("Plugin Users");
for (Map.Entry<String, Integer> entry : getPluginUsers(ProtocolLibrary.getProtocolManager()).entrySet()) {
final int count = entry.getValue();
// Plot plugins of this type
pluginUsers.addPlotter(new Metrics.Plotter(entry.getKey()) {
@Override
public int getValue() {
return count;
}
});
}
}
// Retrieve loaded plugins
private Map<String, Integer> getPluginUsers(ProtocolManager manager) {
Map<String, Integer> users = new HashMap<String, Integer>();
for (PacketListener listener : manager.getPacketListeners()) {
String name = PacketAdapter.getPluginName(listener);
// Increment occurence
if (!users.containsKey(name)) {
users.put(name, 1);
} else {
users.put(name, users.get(name) + 1);
}
}
return users;
}
}
@@ -1,71 +0,0 @@
package com.comphenix.protocol;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.logging.Logger;
import net.minecraft.server.v1_11_R1.DispenserRegistry;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.craftbukkit.v1_11_R1.CraftServer;
import org.bukkit.craftbukkit.v1_11_R1.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.v1_11_R1.util.Versioning;
import com.comphenix.protocol.utility.Constants;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
/**
* Used to ensure that ProtocolLib and Bukkit is prepared to be tested.
*
* @author Kristian
*/
public class BukkitInitialization {
private static boolean initialized;
private static boolean packaged;
/**
* Initialize Bukkit and ProtocolLib such that we can perfrom unit testing.
* @throws IllegalAccessException If we are unable to initialize Bukkit.
*/
public static void initializeItemMeta() throws IllegalAccessException {
if (!initialized) {
// Denote that we're done
initialized = true;
initializePackage();
DispenserRegistry.c(); // Basically registers everything
// Mock the server object
Server mockedServer = mock(Server.class);
when(mockedServer.getLogger()).thenReturn(Logger.getLogger("Minecraft"));
when(mockedServer.getName()).thenReturn("Mock Server");
when(mockedServer.getVersion()).thenReturn(CraftServer.class.getPackage().getImplementationVersion());
when(mockedServer.getBukkitVersion()).thenReturn(Versioning.getBukkitVersion());
when(mockedServer.getItemFactory()).thenReturn(CraftItemFactory.instance());
when(mockedServer.isPrimaryThread()).thenReturn(true);
// Inject this fake server
Bukkit.setServer(mockedServer);
}
}
/**
* Ensure that package names are correctly set up.
*/
public static void initializePackage() {
if (!packaged) {
packaged = true;
MinecraftReflection.setMinecraftPackage(Constants.NMS, Constants.OBC);
MinecraftVersion.setCurrentVersion(MinecraftVersion.FROSTBURN_UPDATE);
}
}
}
@@ -1,75 +0,0 @@
/**
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.util.Map;
import java.util.Map.Entry;
import net.minecraft.server.v1_11_R1.PacketLoginInStart;
import org.junit.BeforeClass;
import org.junit.Test;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.netty.NettyProtocolRegistry;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
/**
* @author dmulloy2
*/
public class PacketTypeTest {
@BeforeClass
public static void initializeReflection() {
BukkitInitialization.initializePackage();
}
@Test
public void testFindCurrent() {
assertEquals(PacketType.Play.Client.STEER_VEHICLE, PacketType.findCurrent(Protocol.PLAY, Sender.CLIENT, "SteerVehicle"));
}
@Test
public void testLoginStart() {
// This packet is critical for handleLoin
assertEquals(PacketLoginInStart.class, PacketType.Login.Client.START.getPacketClass());
}
@Test
public void ensureAllExist() {
boolean missing = false;
ProtocolRegistry registry = new NettyProtocolRegistry();
Map<PacketType, Class<?>> lookup = registry.getPacketTypeLookup();
for (Entry<PacketType, Class<?>> entry : lookup.entrySet()) {
PacketType type = entry.getKey();
Class<?> clazz = entry.getValue();
if (type.isDynamic()) {
System.err.println("Packet " + clazz + " does not have a corresponding PacketType!");
missing = true;
}
//assertFalse("Packet " + clazz + " does not have a corresponding PacketType!", type.isDynamic());
}
assertFalse("There are packets that aren\'t accounted for!", missing);
}
}
@@ -1,21 +0,0 @@
package com.comphenix.protocol.utility;
import static org.junit.Assert.assertNotNull;
import org.junit.BeforeClass;
import org.junit.Test;
import com.comphenix.protocol.BukkitInitialization;
public class MinecraftMethodsTest {
@BeforeClass
public static void initializeReflection() {
BukkitInitialization.initializePackage();
}
@Test
public void testSendPacketMethod() {
assertNotNull(MinecraftMethods.getSendPacketMethod());
}
}
@@ -1,25 +0,0 @@
package com.comphenix.protocol.wrappers;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import com.comphenix.protocol.BukkitInitialization;
@RunWith(org.powermock.modules.junit4.PowerMockRunner.class)
@PowerMockIgnore({ "org.apache.log4j.*", "org.apache.logging.*", "org.bukkit.craftbukkit.libs.jline.*" })
//@PrepareForTest(CraftItemFactory.class)
public class WrappedWatchableObjectTest {
//@BeforeClass
public static void initializeBukkit() throws IllegalAccessException {
BukkitInitialization.initializeItemMeta();
}
//@Test
/* public void testItemStack() {
final ItemStack stack = new ItemStack(Material.GOLD_AXE);
final WrappedWatchableObject test = new WrappedWatchableObject(0, stack);
ItemStack value = (ItemStack) test.getValue();
assertEquals(value.getType(), stack.getType());
} */
}
+329 -94
View File
@@ -1,94 +1,329 @@
<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-Parent</artifactId>
<version>v4-SNAPSHOT</version>
<name>ProtocolLib-Parent</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<modules>
<module>modules/API</module>
<module>modules/ProtocolLib</module>
</modules>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>http://www.spigotmc.org/resources/protocollib.1997/</url>
<packaging>pom</packaging>
<inceptionYear>2012</inceptionYear>
<distributionManagement>
<repository>
<id>dmulloy2-releases</id>
<url>http://repo.dmulloy2.net/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>dmulloy2-snapshots</id>
<url>http://repo.dmulloy2.net/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>http://repo.dmulloy2.net/content/groups/public/</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
</repositories>
<build>
<defaultGoal>clean package install</defaultGoal>
<finalName>${project.name}</finalName>
<sourceDirectory>src/main/java</sourceDirectory>
<!--
<resources>
<resource>
<targetPath>.</targetPath>
<filtering>true</filtering>
<directory>src/main/resources/</directory>
<includes>
<include>*.yml</include>
</includes>
</resource>
</resources>
-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</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>
</configuration>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>aggregate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<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.6.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.7</powermock.version>
<spigot.version>1.16.4-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.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.bytebuddy</pattern>
<shadedPattern>com.comphenix.net.bytebuddy</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-M5</version>
<configuration>
<trimStackTrace>false</trimStackTrace>
<useFile>false</useFile>
<systemProperties>
<property>
<name>projectVersion</name>
<value>${project.version}</value>
</property>
</systemProperties>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</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.1</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.2.0</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>
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.16</version>
</dependency>
<!-- Testing dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.5.10</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>
@@ -42,83 +42,63 @@ public interface AsynchronousManager {
* @param listener - the packet listener that will receive these asynchronous events.
* @return An asynchronous handler.
*/
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
AsyncListenerHandler registerAsyncHandler(PacketListener listener);
/**
* Unregisters and closes the given asynchronous handler.
* @param handler - asynchronous handler.
*/
public abstract void unregisterAsyncHandler(AsyncListenerHandler handler);
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);
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();
void unregisterAsyncHandlers(Plugin plugin);
/**
* 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();
Set<PacketType> getSendingTypes();
/**
* 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();
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);
boolean hasAsynchronousListeners(PacketEvent packet);
/**
* Retrieve the default packet stream.
* @return Default packet stream.
*/
public abstract PacketStream getPacketStream();
PacketStream getPacketStream();
/**
* Retrieve the default error reporter.
* @return Default reporter.
*/
public abstract ErrorReporter getErrorReporter();
ErrorReporter getErrorReporter();
/**
* Remove listeners, close threads and transmit every delayed packet.
*/
public abstract void cleanupAll();
void cleanupAll();
/**
* Signal that a packet is ready to be transmitted.
@@ -127,29 +107,29 @@ public interface AsynchronousManager {
* has been called previously.
* @param packet - packet to signal.
*/
public abstract void signalPacketTransmission(PacketEvent packet);
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);
void registerTimeoutHandler(PacketListener listener);
/**
* Unregisters a given timeout listener.
* @param listener - the timeout listener to unregister.
*/
public abstract void unregisterTimeoutHandler(PacketListener listener);
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();
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();
Set<PacketListener> getAsyncHandlers();
}
@@ -29,13 +29,12 @@ 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.utility.ByteBuddyGenerated;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.ErrorReporter;
@@ -77,11 +76,6 @@ class CommandPacket extends CommandBase {
*/
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;
@@ -371,7 +365,7 @@ class CommandPacket extends CommandBase {
logger.info("Initial packet:\n" + original + " -> ");
}
logger.info(shortDescription + ":\n" + getPacketDescription(
logger.info(shortDescription + ":\n" + HexDumper.getPacketDescription(
event.getPacket())
);
} catch (IllegalAccessException e) {
@@ -428,7 +422,7 @@ class CommandPacket extends CommandBase {
*/
private void savePacketState(PacketEvent event) {
try {
originalPackets.put(event, getPacketDescription(event.getPacket()));
originalPackets.put(event, HexDumper.getPacketDescription(event.getPacket()));
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot read packet.", e);
}
@@ -450,68 +444,6 @@ class CommandPacket extends CommandBase {
}
};
}
/**
* 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) {
@@ -23,19 +23,7 @@ import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.apache.commons.io.HexDump;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import java.util.logging.*;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
@@ -45,6 +33,13 @@ 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
@@ -99,16 +94,16 @@ public class PacketLogging implements CommandExecutor, PacketListener {
type = PacketType.findCurrent(protocol, pSender, id);
} catch (NumberFormatException ex) { // Check packet names
String name = args[2];
outer: for (PacketType packet : PacketType.values()) {
for (PacketType packet : PacketType.values()) {
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
if (packet.name().equalsIgnoreCase(name)) {
type = packet;
break outer;
break;
}
for (String className : packet.getClassNames()) {
if (className.equalsIgnoreCase(name)) {
type = packet;
break outer;
break;
}
}
}
@@ -248,9 +243,8 @@ public class PacketLogging implements CommandExecutor, PacketListener {
return plugin;
}
private static enum LogLocation {
CONSOLE,
FILE;
private enum LogLocation {
CONSOLE, FILE
}
private static class LogFormatter extends Formatter {
@@ -15,7 +15,7 @@
* 02111-1307 USA
*/
package com.comphenix.protocol.reflect;
package com.comphenix.protocol;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@@ -23,8 +23,10 @@ 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.
@@ -33,40 +35,53 @@ import com.google.common.collect.HashBiMap;
* want to prevent the creation of additional members dynamically.
* @author Kristian
*/
public class ObjectEnum<T> implements Iterable<T> {
public class PacketTypeEnum implements Iterable<PacketType> {
// Used to convert between IDs and names
protected BiMap<T, String> members = HashBiMap.create();
protected Set<PacketType> members = Sets.newHashSet();
/**
* Registers every declared integer field.
* @param fieldType Field type
* Registers every declared PacketType field.
*/
public ObjectEnum(Class<T> fieldType) {
registerAll(fieldType);
public PacketTypeEnum() {
registerAll();
}
/**
* Registers every public assignable static field as a member.
* @param fieldType Field type
*/
@SuppressWarnings("unchecked")
protected void registerAll(Class<T> fieldType) {
protected void registerAll() {
try {
// Register every non-deprecated field
for (Field entry : this.getClass().getFields()) {
if (Modifier.isStatic(entry.getModifiers()) && fieldType.isAssignableFrom(entry.getType())) {
T value = (T) entry.get(null);
if (value == null)
throw new IllegalArgumentException("Field " + entry + " was NULL. Remember to " +
"construct the object after the field has been declared.");
registerMember(value, entry.getName());
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 (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (Exception ex) {
ex.printStackTrace();
}
}
@@ -76,11 +91,14 @@ public class ObjectEnum<T> implements Iterable<T> {
* @param name - name of member.
* @return TRUE if the member was registered, FALSE otherwise.
*/
public boolean registerMember(T instance, String name) {
if (!members.containsKey(instance)) {
members.put(instance, name);
public boolean registerMember(PacketType instance, String name) {
instance.setName(name);
if (!members.contains(instance)) {
members.add(instance);
return true;
}
return false;
}
@@ -89,38 +107,36 @@ public class ObjectEnum<T> implements Iterable<T> {
* @param member - the member to check.
* @return TRUE if the given member has been registered, FALSE otherwise.
*/
public boolean hasMember(T member) {
return members.containsKey(member);
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
*/
public T valueOf(String name) {
return members.inverse().get(name);
@Deprecated
public PacketType valueOf(String name) {
for (PacketType member : members) {
if (member.name().equals(name))
return member;
}
return null;
}
/**
* Retrieve the name of the given member.
* @param member - the member to retrieve.
* @return Declared name of the member, or NULL if not found.
*/
public String getDeclaredName(T member) {
return members.get(member);
}
/**
* Retrieve every registered member.
* @return Enumeration of every value.
*/
public Set<T> values() {
return new HashSet<T>(members.keySet());
public Set<PacketType> values() {
return new HashSet<>(members);
}
@Override
public Iterator<T> iterator() {
return members.keySet().iterator();
public Iterator<PacketType> iterator() {
return members.iterator();
}
}
@@ -105,16 +105,6 @@ class PacketTypeLookup {
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);
@@ -26,21 +26,8 @@ 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.error.*;
import com.comphenix.protocol.injector.DelayedSingleTask;
import com.comphenix.protocol.injector.InternalManager;
import com.comphenix.protocol.injector.PacketFilterManager;
@@ -50,13 +37,18 @@ 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.ByteBuddyFactory;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.Util;
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;
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;
/**
* The main entry point for ProtocolLib.
@@ -113,10 +105,6 @@ public class ProtocolLib extends JavaPlugin {
// Metrics and statistics
private Statistics statistics;
// Executors
private static ListeningScheduledExecutorService executorAsync;
private static ListeningScheduledExecutorService executorSync;
// Structure compiler
private BackgroundCompiler backgroundCompiler;
@@ -155,20 +143,8 @@ public class ProtocolLib extends JavaPlugin {
logger = getLogger();
ProtocolLogger.init(this);
int java = Util.getJavaVersion();
if (java != -1 && java < 8 && !getConfig().getBoolean("ignoreJava", false)) {
logger.warning("Detected outdated Java version: Java " + java);
logger.warning("It is recommended that you update to Java 8 as soon as possible.");
logger.warning("Future versions of ProtocolLib many not support Java " + java + ".");
logger.warning("Java 8 will allow for much faster reflection performance.");
}
// Initialize enhancer factory
EnhancerFactory.getInstance().setClassLoader(getClassLoader());
// Initialize executors
executorAsync = BukkitExecutors.newAsynchronous(this);
executorSync = BukkitExecutors.newSynchronous(this);
ByteBuddyFactory.getInstance().setClassLoader(getClassLoader());
// Add global parameters
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
@@ -222,7 +198,7 @@ public class ProtocolLib extends JavaPlugin {
.build();
// Initialize the API
ProtocolLibrary.init(this, config, protocolManager, reporter, executorAsync, executorSync);
ProtocolLibrary.init(this, config, protocolManager, reporter);
// Setup error reporter
detailedReporter.addGlobalParameter("manager", protocolManager);
@@ -246,8 +222,6 @@ public class ProtocolLib extends JavaPlugin {
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
disablePlugin();
@@ -277,8 +251,6 @@ public class ProtocolLib extends JavaPlugin {
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (LinkageError e) {
logger.warning("Failed to register command " + command.name() + ": " + e);
} catch (Throwable e) {
@@ -419,8 +391,6 @@ public class ProtocolLib extends JavaPlugin {
createPacketTask(server);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e));
disablePlugin();
@@ -434,8 +404,6 @@ public class ProtocolLib extends JavaPlugin {
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (IOException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(statistics));
} catch (Throwable e) {
@@ -502,7 +470,7 @@ public class ProtocolLib extends JavaPlugin {
File[] candidates = pluginFolder.listFiles();
if (candidates != null) {
for (File candidate : pluginFolder.listFiles()) {
for (File candidate : candidates) {
if (candidate.isFile() && !candidate.equals(loadedFile)) {
Matcher match = ourPlugin.matcher(candidate.getName());
if (match.matches()) {
@@ -585,8 +553,6 @@ public class ProtocolLib extends JavaPlugin {
}, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
if (packetTask == -1) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CREATE_TIMEOUT_TASK).error(e));
@@ -673,4 +639,8 @@ public class ProtocolLib extends JavaPlugin {
public Statistics getStatistics() {
return statistics;
}
public ProtocolConfig getProtocolConfig() {
return config;
}
}
@@ -19,12 +19,11 @@ 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;
import org.apache.commons.lang.Validate;
import org.bukkit.plugin.Plugin;
/**
* The main entry point for ProtocolLib.
@@ -39,12 +38,12 @@ public class ProtocolLibrary {
/**
* The maximum version ProtocolLib has been tested with.
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "1.11.2";
public static final String MAXIMUM_MINECRAFT_VERSION = "1.16.5";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.11) was released.
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.16.5) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2016-12-21";
public static final String MINECRAFT_LAST_RELEASE_DATE = "2021-01-15";
/**
* Plugins that are currently incompatible with ProtocolLib.
@@ -56,21 +55,15 @@ public class ProtocolLibrary {
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) {
protected static void init(Plugin plugin, ProtocolConfig config, ProtocolManager manager, ErrorReporter reporter) {
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;
}
@@ -106,26 +99,6 @@ public class ProtocolLibrary {
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.
*/
@@ -18,6 +18,7 @@ package com.comphenix.protocol;
import java.text.MessageFormat;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
@@ -25,18 +26,20 @@ import org.bukkit.plugin.Plugin;
* @author dmulloy2
*/
public class ProtocolLogger {
private static Plugin plugin;
private static boolean debugEnabled = false;
private static Logger logger = Logger.getLogger("Minecraft");
protected static void init(Plugin plugin) {
ProtocolLogger.plugin = plugin;
}
/**
* Don't call this method from any plugin. Currently only public to test if it fixes a weird error.
* See GH-740
* @param plugin ProtocolLib
*/
public static void init(ProtocolLib plugin) {
logger = plugin.getLogger();
private static boolean isDebugEnabled() {
try {
return plugin.getConfig().getBoolean("global.debug", false);
} catch (Throwable ex) { // Maybe we're testing or something
return false;
}
debugEnabled = plugin.getConfig().getBoolean("global.debug", false);
} catch (Throwable ignored) { }
}
/**
@@ -46,7 +49,7 @@ public class ProtocolLogger {
* @param args Arguments to format in
*/
public static void log(Level level, String message, Object... args) {
plugin.getLogger().log(level, MessageFormat.format(message, args));
logger.log(level, MessageFormat.format(message, args));
}
/**
@@ -65,12 +68,18 @@ public class ProtocolLogger {
* @param ex Exception to log
*/
public static void log(Level level, String message, Throwable ex) {
plugin.getLogger().log(level, message, ex);
logger.log(level, message, ex);
}
public static void debug(String message, Object... args) {
if (isDebugEnabled()) {
if (debugEnabled) {
log("[Debug] " + message, args);
}
}
public static void debug(String message, Throwable ex) {
if (debugEnabled) {
logger.log(Level.WARNING, "[Debug] " + message, ex);
}
}
}
@@ -49,7 +49,7 @@ public interface ProtocolManager extends PacketStream {
* @param player - the player.
* @return The associated protocol version, or {@link Integer#MIN_VALUE} if unknown.
*/
public int getProtocolVersion(Player player);
int getProtocolVersion(Player player);
/**
* Send a packet to the given player.
@@ -63,7 +63,7 @@ public interface ProtocolManager extends PacketStream {
* @throws InvocationTargetException - if an error occurred when sending the packet.
*/
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
throws InvocationTargetException;
/**
@@ -79,7 +79,7 @@ public interface ProtocolManager extends PacketStream {
* @throws IllegalAccessException If the underlying method caused an error.
*/
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
throws IllegalAccessException, InvocationTargetException;
/**
@@ -87,7 +87,7 @@ public interface ProtocolManager extends PacketStream {
* @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);
void broadcastServerPacket(PacketContainer packet);
/**
* Broadcast a packet to every player that is receiving information about a given entity.
@@ -99,7 +99,7 @@ public interface ProtocolManager extends PacketStream {
* @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);
void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker);
/**
* Broadcast a packet to every player within the given maximum observer distance.
@@ -107,13 +107,13 @@ public interface ProtocolManager extends PacketStream {
* @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);
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();
ImmutableSet<PacketListener> getPacketListeners();
/**
* Adds a packet listener.
@@ -123,7 +123,7 @@ public interface ProtocolManager extends PacketStream {
* can register it again.
* @param listener - new packet listener.
*/
public void addPacketListener(PacketListener listener);
void addPacketListener(PacketListener listener);
/**
* Removes a given packet listener.
@@ -131,47 +131,20 @@ public interface ProtocolManager extends PacketStream {
* Attempting to remove a listener that doesn't exist has no effect.
* @param listener - the packet listener to remove.
*/
public void removePacketListener(PacketListener listener);
void removePacketListener(PacketListener listener);
/**
* Removes every listener associated with the given plugin.
* @param plugin - the plugin to unload.
*/
public void removePacketListeners(Plugin plugin);
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);
PacketContainer createPacket(PacketType type);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
@@ -185,18 +158,7 @@ public interface ProtocolManager extends PacketStream {
* @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);
PacketContainer createPacket(PacketType type, boolean forceDefaults);
/**
* Construct a packet using the special builtin Minecraft constructors.
@@ -204,7 +166,7 @@ public interface ProtocolManager extends PacketStream {
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
/**
* Completely resend an entity to a list of clients.
@@ -214,7 +176,7 @@ public interface ProtocolManager extends PacketStream {
* @param entity - entity to refresh.
* @param observers - the clients to update.
*/
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
/**
* Retrieve the associated entity.
@@ -223,7 +185,7 @@ public interface ProtocolManager extends PacketStream {
* @return The associated entity.
* @throws FieldAccessException Reflection failed.
*/
public Entity getEntityFromID(World container, int id) throws FieldAccessException;
Entity getEntityFromID(World container, int id) throws FieldAccessException;
/**
* Retrieve every client that is receiving information about a given entity.
@@ -231,55 +193,37 @@ public interface ProtocolManager extends PacketStream {
* @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();
List<Player> getEntityTrackers(Entity entity) throws FieldAccessException;
/**
* 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();
Set<PacketType> getSendingFilterTypes();
/**
* 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();
Set<PacketType> getReceivingFilterTypes();
/**
* Retrieve the current Minecraft version.
* @return The current version.
*/
public MinecraftVersion getMinecraftVersion();
MinecraftVersion getMinecraftVersion();
/**
* Determines whether or not this protocol manager has been disabled.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClosed();
boolean isClosed();
/**
* Retrieve the current asynchronous packet manager.
* @return Asynchronous packet manager.
*/
public AsynchronousManager getAsynchronousManager();
AsynchronousManager getAsynchronousManager();
public void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist);
void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist);
}
@@ -23,10 +23,6 @@ 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;
@@ -37,12 +33,15 @@ 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;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
/**
* Represents a filter manager for asynchronous packets.
* <p>
@@ -88,7 +87,7 @@ public class AsyncFilterManager implements AsynchronousManager {
// Initialize timeout listeners
this.serverTimeoutListeners = new SortedPacketListenerList();
this.clientTimeoutListeners = new SortedPacketListenerList();
this.timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
this.timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap<>());
this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners);
this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
@@ -318,31 +317,21 @@ public class AsyncFilterManager implements AsynchronousManager {
// The player is only be null when they're logged out,
// so this should be a pretty safe check
Player player = newEvent.getPlayer();
Player player = syncPacket.getPlayer();
if (player != null) {
// Start the process
getSendingQueue(syncPacket).enqueue(newEvent);
// We know this is occuring on the main thread, so pass TRUE
// 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();
@@ -24,8 +24,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
@@ -36,10 +34,11 @@ import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.timing.TimedListenerManager;
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
import com.comphenix.protocol.timing.TimedTracker;
import com.comphenix.protocol.utility.WrappedScheduler;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import org.bukkit.plugin.Plugin;
/**
* Represents a handler for an asynchronous event.
* <p>
@@ -345,8 +344,7 @@ public class AsyncListenerHandler {
}
private void scheduleAsync(Runnable runnable) {
// Handle deprecation
WrappedScheduler.runAsynchronouslyRepeat(listener.getPlugin(), filterManager.getScheduler(), runnable, 0L, -1L);
listener.getPlugin().getServer().getScheduler().runTaskAsynchronously(listener.getPlugin(), runnable);
}
/**
@@ -1,5 +1,17 @@
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;
@@ -7,18 +19,6 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.bukkit.entity.Player;
import com.comphenix.protocol.utility.SafeCacheBuilder;
import com.comphenix.protocol.utility.Util;
import com.google.common.base.Function;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.UncheckedExecutionException;
/**
* Represents a concurrent player map.
* <p>
@@ -37,7 +37,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
ADDRESS {
@Override
public Object apply(Player player) {
return player.getAddress();
return player == null ? null : player.getAddress();
}
},
@@ -47,7 +47,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
NAME {
@Override
public Object apply(Player player) {
return player.getName();
return player == null ? null : player.getName();
}
},
}
@@ -55,17 +55,17 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
/**
* An internal map of player keys to values.
*/
protected ConcurrentMap<Object, TValue> valueLookup = createValueMap();
private ConcurrentMap<Object, TValue> valueLookup = createValueMap();
/**
* A cache of the associated keys for each player.
*/
protected ConcurrentMap<Object, Player> keyLookup = createKeyCache();
private ConcurrentMap<Object, Player> keyLookup = createKeyCache();
/**
* The method used to retrieve a unique key for a player.
*/
protected final Function<Player, Object> keyMethod;
private final Function<Player, Object> keyMethod;
/**
* Construct a new concurrent player map that uses each player's address as key.
@@ -89,7 +89,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* Construct a new concurrent player map using the given standard key method.
* @param standardMethod - the standard key method.
*/
public ConcurrentPlayerMap(PlayerKey standardMethod) {
private ConcurrentPlayerMap(PlayerKey standardMethod) {
this.keyMethod = standardMethod;
}
@@ -107,7 +107,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* The default implementation uses a {@link ConcurrentHashMap}.
* @return The value map.
*/
protected ConcurrentMap<Object, TValue> createValueMap() {
private ConcurrentMap<Object, TValue> createValueMap() {
return Maps.newConcurrentMap();
}
@@ -115,19 +115,16 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* Construct a cache of keys and the associated player.
* @return The key map.
*/
protected ConcurrentMap<Object, Player> createKeyCache() {
private ConcurrentMap<Object, Player> createKeyCache() {
return SafeCacheBuilder.newBuilder().
weakValues().
removalListener(
new RemovalListener<Object, Player>() {
@Override
public void onRemoval(RemovalNotification<Object, Player> removed) {
// We ignore explicit removal
if (removed.wasEvicted()) {
onCacheEvicted(removed.getKey());
}
}
}).
(RemovalListener<Object, Player>) removed -> {
// We ignore explicit removal
if (removed.wasEvicted()) {
onCacheEvicted(removed.getKey());
}
}).
build(
new CacheLoader<Object, Player>() {
@Override
@@ -147,7 +144,6 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
/**
* Invoked when an entry in the cache has been evicted, typically by the garbage collector.
* @param key - the key.
* @param player - the value that was evicted or collected.
*/
private void onCacheEvicted(Object key) {
Player newPlayer = findOnlinePlayer(key);
@@ -165,7 +161,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* @param key - a non-null key.
* @return The player with the given key, or NULL if not found.
*/
protected Player findOnlinePlayer(Object key) {
private Player findOnlinePlayer(Object key) {
for (Player player : Util.getOnlinePlayers()) {
if (key.equals(keyMethod.apply(player))) {
return player;
@@ -179,7 +175,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* @param key - the key of the player we are locating.
* @return The player, or NULL if not found.
*/
protected Player lookupPlayer(Object key) {
private Player lookupPlayer(Object key) {
try {
return keyLookup.get(key);
} catch (UncheckedExecutionException e) {
@@ -192,7 +188,9 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* @param player - the player whose key we want to retrieve.
* @return The key.
*/
protected Object cachePlayerKey(Player player) {
private Object cachePlayerKey(Player player) {
Preconditions.checkNotNull(player, "player cannot be null");
Object key = keyMethod.apply(player);
keyLookup.put(key, player);
Loaded 100 of 357 files, more files were not shown because too many files have changed in this diff. Show more