Compare commits

..
192 Commits
Author SHA1 Message Date
Dan Mulloy d9c623a254 backwards compat 2022-12-13 20:32:53 -05:00
Dan Mulloy fe0abbf9b3 revert some changes to MinecraftReflection 2022-12-13 20:30:51 -05:00
Dan Mulloy 92b604506f line endings 2022-12-13 20:25:07 -05:00
derklaro 574c34ad5c remove unneeded packet class names 2022-12-08 09:33:29 +01:00
derklaro 115cc83040 resolve conflicts 2022-12-08 09:14:04 +01:00
derklaro a18c418f42 1.19.3 2022-12-08 09:03:45 +01:00
Dan Mulloy 6aaf0ec26b Normalize line endings to LF 2022-12-07 13:52:09 -05:00
lennoxlotl 86e586da26 Fix not being able to read effects from RemoveEntityEffect packet (#1908)
fix being unable to read from RemoveEntityEffect due to wrong alias order
2022-11-26 22:54:33 +00:00
Nassim Jahnke 069783a353 Fix getEntityClass full qualifier (#1967) 2022-11-26 17:49:35 -05:00
opl- cc95e19ba4 Fix Magma support (#1953)
Tested with Magma v1.16.5 and Paper v1.18.2
2022-11-25 15:13:33 -05:00
derklaro 5ef24a7471 22w43a 2022-10-27 16:04:17 +02:00
derklaro 475f89ab89 deprecate WrappedServerPing chat preview stuff 2022-10-20 10:10:41 +02:00
derklaro e3d4339659 player info action changes 2022-10-19 20:04:33 +02:00
derklaro e7d54cdabd update to 1.19.3 2022-10-19 16:24:10 +02:00
Pasqual Koschmieder c31133c20f Improve performance by removing hash computations of packets (#1933) 2022-10-16 20:31:42 +00:00
caoli5288 69ae3656b5 Fix StreamSerializer backwards compatibility (#1868)
Fixes #1813
2022-10-16 18:37:08 +00:00
Pasqual Koschmieder 43145bd478 cleanup MonitorAdapter (#1831) 2022-08-24 00:53:55 +00:00
Nassim Jahnke b7c1e096c4 Support Mojang mapped servers (#1869) 2022-08-23 20:44:48 -04:00
Pasqual Koschmieder 6707c4811e only run inbound listeners on the main thread if requested (#1851) 2022-08-16 23:51:54 -04:00
Rodney 2092b8f48e Make getRandomKey() properly random (#1834) 2022-08-13 15:08:01 +00:00
Pasqual Koschmieder 20e73369fa mark some critical packets as forced-async (#1840) 2022-08-13 11:04:17 -04:00
Pasqual Koschmieder abc0db8281 remove off-event-loop logic from inbound packet listeners (#1836) 2022-08-12 17:29:07 -04:00
Pasqual Koschmieder 1beb95115f Improve cloning tests (#1822) 2022-08-12 02:07:38 +00:00
Pasqual Koschmieder 7fcfcdc365 fix fix of fix of getEntityFromId (#1827) 2022-08-11 22:04:27 -04:00
Pasqual Koschmieder 2be216899a use correct packet for chat extensions on 1.19+ (#1824) 2022-08-10 20:59:03 +00:00
Pasqual Koschmieder 8876ce323b add support for sync & async receiving listeners (#1815) 2022-08-10 20:50:33 +00:00
Pasqual Koschmieder 575174580e Small update for 1.19.2 (#1814) 2022-08-10 16:49:01 -04:00
Miklas 7fd4ec3172 Support for 1.18+ ClientboundLevelChunkWithLightPacket (#1592) 2022-08-06 18:52:42 -04:00
Pasqual Koschmieder a75d383001 fix injection exception when plugin is disabled (#1798) 2022-08-02 20:59:33 -04:00
Pasqual Koschmieder 7ddfd4f347 Fix & improve PacketContainer serialization & cloning (#1794) 2022-07-31 11:54:26 -04:00
Pasqual Koschmieder 7e137cbfc5 dependency updates (#1790) 2022-07-30 20:01:11 -04:00
Pasqual Koschmieder d40762e69d Update to 1.19.1 (#1699)
* add support for enforceSecureChat in ServerPing
* remove security exception check from test
2022-07-27 17:09:25 -04:00
Snowiiii ae19478007 Updated Dependencies (#1769) 2022-07-26 17:31:58 +00:00
games647 11a8184c3e Add StructureModifier for extracting the signature data in chat and login packets (#1742) 2022-07-26 13:29:34 -04:00
Pasqual Koschmieder c3dc00de05 fix two smaller issues (#1774) 2022-07-26 13:20:07 -04:00
Dan Mulloy 84cb541866 Merge branch 'pr-1659' 2022-07-24 11:07:39 -04:00
Photon-Github 624f6aaca6 Anonymous Class Replacement 2022-07-24 11:02:56 -04:00
Pasqual Koschmieder c5f0550953 Use MethodHandles for reflection (#1561)
* don't enforce async calls for thread-safe listeners (closes #1551)
* cleanups, remove structure compiling
* improve cloning a bit
* fix small issue in no-op structure modifier
* remove last usages of FieldUtils
* improve and fix equality check in container test
2022-07-24 10:16:05 -04:00
Pasqual Koschmieder 7f0bc7fd24 don't suppress deserialization issues (#1759) 2022-07-24 10:08:35 -04:00
Pasqual Koschmieder 96155b1065 Improve getEntityFromID (#1740) 2022-07-16 11:09:07 -04:00
Pasqual Koschmieder 23dac3287b detect server reloads and print a big warning message (#1733) 2022-07-16 11:00:01 -04:00
Pasqual Koschmieder 0bbbd961aa synchronize collections with possible concurrent accesses (#1723) 2022-07-07 07:51:06 +00:00
Pasqual Koschmieder dd19e1040a [ci skip] cleanup & improve readme a bit (#1724)
* [ci skip] cleanup & improve readme a bit

* [ci skip] improve code indention
2022-07-07 08:49:41 +01:00
Dan Mulloy e45c16d490 Missed a hyphen 2022-06-29 19:23:21 -04:00
Dan Mulloy a19959cea4 Update issue templates 2022-06-29 19:22:25 -04:00
Pasqual Koschmieder 4f18d37832 fix exceptions when reloading the server (#1689) 2022-06-29 19:08:14 -04:00
Photon-GitHubandDan Mulloy a2bf242097 Cleanup (#1603)
* Replace guava Charsets with StandardCharsets.

* Use try-with-resources

* Faster Util asList, inline getOnlinePlayers.

* Use direct ArrayList allocation.

* Use new instead of Lists#...

* Use new instead of Lists#...

* Faster looping.

* Use switch.

* Remove diamond operators.

* Use ArrayDeque instead of LinkedList.

* Actually conform to the documentation and always use an ArrayList as backing list.

* Potentially breaking change: Use switch instead of startsWith as this e.g. causes "n" to be interpreted as "names" and any future new commands starting with "n" will be ignored.

* Use addAll().

* Remove IntegerSet. Unused and also covered by fastutils IntSet.

* Much faster boolean parsing. Might have breaking changes as parameterName is now checked after the false block.

* Make most fields final, fix JavaDoc and remove diamond operators.

* Make fields final.

* Much cleaner getAllInterfaces() method.

Co-authored-by: Dan Mulloy <dev@dmulloy2.net>
2022-06-25 11:32:42 -04:00
Pasqual Koschmieder 4e105c59ed fix exceptions on older server versions (#1645) 2022-06-14 12:54:45 -04:00
Pasqual Koschmieder 59ca841ed5 correctly mark changed packet as processed (#1639) 2022-06-13 10:01:27 -04:00
Dan Mulloy 5e8f044a18 Run codeql analysis (#1630) 2022-06-11 13:03:03 -04:00
Pasqual Koschmieder 4db1e39ac7 fix packet listener calling when processed in event loop (#1621) 2022-06-11 12:36:46 -04:00
Pasqual Koschmieder e202503c09 allow setting if chat preview is enabled in WrappedServerPing (#1623) 2022-06-11 12:35:12 -04:00
Pasqual Koschmieder aed98abac6 fix registration of parameterized registry types (#1628) 2022-06-11 12:32:59 -04:00
Pasqual Koschmieder 250f94e9cd fix invalid packet id setting (#1629) 2022-06-11 12:30:38 -04:00
Pasqual Koschmieder d7bf43001f expand and document WrappedProfilePublicKey (#1618) 2022-06-11 11:58:08 -04:00
Pasqual Koschmieder 868b357527 add missing enum constants added in 1.19 (#1617) 2022-06-11 11:57:50 -04:00
Pasqual Koschmieder 9a609c2053 fix compile issues introduced by latest spigot api changes (#1622) 2022-06-11 11:57:25 -04:00
Pasqual Koschmieder f3acce99d8 improve and fix equality check in container test (#1614) 2022-06-08 16:33:22 -04:00
Pasqual Koschmieder 374e6cd5ee allow easier access to registries (#1613) 2022-06-08 16:32:52 -04:00
Pasqual Koschmieder 84a0b5ffdd allow easier access to converters (#1610) 2022-06-08 13:34:01 -04:00
Pasqual Koschmieder 4cc3957723 bring back raw text component creation (#1612) 2022-06-08 13:33:07 -04:00
Pasqual Koschmieder a0a5469988 Update to Minecraft 1.19 (#1601) 2022-06-07 21:24:31 -04:00
Photon-GitHub 240920d642 Diamond inference cleanup (#1593) 2022-05-25 23:30:08 -04:00
Pasqual Koschmieder 764195bd55 switch to a queue for queued scheduled packets (#1598) 2022-05-25 23:24:59 -04:00
Pasqual Koschmieder 7bfee67a29 fix packet listener invocation when packet is sent async (#1587) 2022-05-04 00:22:00 -04:00
Pasqual Koschmieder c87604cf0c don't enforce async calls for thread-safe listeners (#1555)
closes #1551
2022-03-26 13:00:22 -04:00
Pasqual Koschmieder 4096952c16 remove setCancelled / isCancelled from PacketInjector (#1553)
closes #1552
2022-03-26 13:00:12 -04:00
Dan Mulloy b4eff32213 Allow chat error messages to be disabled
Fixes #1549
2022-03-26 10:32:03 -04:00
Pasqual Koschmieder d361526371 Fix missing temp player in pre-join channel injectors (#1535)
Closes #1534
2022-03-12 14:33:24 -05:00
Pasqual Koschmieder 0d4e4c818f fix duplicate element creation when wrapping the bootstrap list (#1532)
closes #1530
2022-03-09 23:17:03 -05:00
Pasqual Koschmieder 073bfa2b86 Out/In bound protocol injection improvements (#1524)
* Clear up some stuff, fix location of wire packet encoder
* Ensure that the player injection cache is always up-to-date
* Make uninjection from a channel more reliable
* Don't schedule an empty runnable if there is no need to do that
* Remove unnecessary throw declarations from some methods
* Adjust uninjection to remove the injector reference as well
* improve channel future injection in network manager
2022-03-07 22:09:04 -05:00
Dan Mulloy f0059f39f6 Update version to 5.0.0-SNAPSHOT 2022-03-07 22:07:54 -05:00
Dan Mulloy 41bb4bacb2 Update version to 4.8.0 for release 2022-03-04 16:28:13 -05:00
Pasqual Koschmieder 9487c42985 Update to 1.18.2 (#1521) 2022-02-28 13:36:20 -05:00
derklaro 55f7b67f9d Update to junit 5, remove powermock 2022-02-25 00:11:26 -05:00
Pasqual Koschmieder e44d1e6051 Improve auto wrapper handling (#1518) 2022-02-25 00:02:11 -05:00
Pasqual Koschmieder baecaf4ca4 Remove unsafe reflection trying to access the modifiers field (#1517) 2022-02-24 23:56:22 -05:00
Pasqual Koschmieder 151d4a289f Prevent memory leaks with loads of PacketMarker objects (#1511)
closes #1509
2022-02-24 16:41:43 -05:00
Pasqual Koschmieder 1b4d79b302 Fix compile issues with the latest spigot version (#1510) 2022-02-24 16:18:56 -05:00
Pasqual Koschmieder 74833f8680 Correctly resolve protocol version of player (#1473) 2022-01-02 12:32:46 -06:00
Pasqual Koschmieder e77f8ced4c Update to 1.18.1 (#1461)
No changes required except for registering the protocol version
2021-12-19 20:50:22 -05:00
Dan Mulloy 1cd83e493d Update version to 4.8.0-SNAPSHOT 2021-11-30 14:14:50 -05:00
Pasqual Koschmieder 40b6c66491 Update to minecraft 1.18 (#1446) 2021-11-30 14:10:03 -05:00
LewUwU 723fc2c7ec Update NettyVersion.java (#1438)
The issue was caused due to a change in the pom of netty (for dependencies)

(#Generated by netty-parent/pom.xml
#Mon, 11 Oct 2021 13:22:16 +0200

netty-all.version=4.1.69.Final
netty-all.buildDate=2021-10-11 13\:22\:16 +0200
netty-all.commitDate=2021-10-11 09\:21\:07 +0000
netty-all.shortCommitHash=34a31522f0
netty-all.longCommitHash=34a31522f0145e2d434aaea2ef8ac5ed8d1a91a0
netty-all.repoStatus=dirty)

Is not more netty-common, now is netty-all
2021-11-14 12:09:34 -05:00
Dan Mulloy dd687ce175 Fix NPE in PlayerInfoData equals
Fixes #1400
Closes #1106
2021-11-14 00:41:20 -05:00
Nassim Jahnke 8774d87d59 Add 1.17 long jump pose (#1409) 2021-11-13 23:51:37 -05:00
Dan Mulloy 8361cf078f Fix GH actions build 2021-09-22 13:29:23 -04:00
Rothes dbedab0c14 Check entityID whether is negative or not. (#1362) 2021-09-22 10:51:47 -04:00
Pim van der Loos 5dda8c8ab1 Fix entity tracker methods for 1.17 (#1354)
- In 1.17, EntityTrackerEntries use ServerPlayerConnections instead of EntityPlayers as they did before. This caused the updateEntity to silently fail when removing the players from the trackedPlayers collection (of connections). This was resolved by retrieving the connections of the players before removing them from the list on 1.17+.
The getEntityTrackers method failed because it could not find any players for the same reason. This was resolved by retrieving the player from the connection before retrieving the Bukkit player from the EntityPlayer object when running on 1.17+.
- This fixes dmulloy2/ProtocolLib#1340
2021-07-26 23:51:24 -07:00
Dan Mulloy 4c0c18d7c6 Remove stack trace for list converter
Addresses #1331
2021-07-10 11:09:56 -04:00
Dan Mulloy 9c20455bf6 Update version to 4.7.1-SNAPSHOT 2021-07-10 11:08:35 -04:00
Dan Mulloy 8175443da0 Update to 4.7.0 2021-07-09 17:16:07 -04:00
Dan Mulloy 95a884974a Only set chat UUID if not already set
Fixes #1310
2021-07-08 23:33:17 -04:00
Dan Mulloy 153dd61994 Update to 1.17.1
Fixes #1315
2021-07-08 23:22:56 -04:00
Dan Mulloy 263ec8a36e Add FLYING packet back for backwards compat
Fixes #1275
2021-06-27 18:13:40 -04:00
Julian af46ba4d1a Fix support for Minecraft versions under the bee update (#1276) 2021-06-27 17:57:14 -04:00
Dan Mulloy dd85904642 Metrics: only track each plugin once 2021-06-26 21:22:52 -04:00
Dan Mulloy 7a8fce224e Set mc object regex in 1.17
Fixes #1271
2021-06-26 20:57:39 -04:00
Dan Mulloy 9ca7c91a76 Simplify packet registry 2021-06-24 17:14:00 -04:00
Dan Mulloy 466354cd2c Cache list constructors to save on exceptions 2021-06-24 17:13:40 -04:00
Dan Mulloy 99504dab8f Abstract out structure modifiers to allow internal structures to be read 2021-06-24 17:13:05 -04:00
Dan Mulloy 9b6603e2eb Use fuzzy reflection for effect types 2021-06-21 16:10:58 -04:00
Dan Mulloy 4fc476a125 Use right int 2 object map class for Paper
Addresses #1217
2021-06-20 12:42:41 -04:00
Dan Mulloy 583ed4b58a Update scoreboard team class
Addresses #1232
2021-06-20 12:35:03 -04:00
Dan Mulloy 638e81b9ce Merge branch 'master' of https://github.com/dmulloy2/ProtocolLib 2021-06-20 12:08:52 -04:00
Dan Mulloy 9a0703d05d Fix attribute builder in 1.17
Addresses #1224
2021-06-20 12:08:47 -04:00
Pasqual Koschmieder 9de096f783 Fix sdk command for jitpack builds (#1241) 2021-06-20 11:51:55 -04:00
Dan Mulloy c54a99945d Try fixing some of the unit tests 2021-06-19 22:45:17 -04:00
Pasqual Koschmieder 90a38cc15c Restore backwards compatibility (#1235)
* Remove usages of net.minecraft and craftbukkit
* Restore packet type backward compatibility (tested on 1.8)
* Re-add last removed packets
* Fix sub class naming for newer minecraft versions
2021-06-19 22:20:54 -04:00
Dan Mulloy 76930ae6e8 Remove travis
It's gonna be a lot of work to build, so we're just gonna use GitHub actions
2021-06-19 22:15:03 -04:00
Pasqual Koschmieder d745cfb184 Switch to GH-Workflow & use java 16 on jitpack builds (#1236)
* Add github actions workflow
* Add jitpack.yml to configure java version
2021-06-19 22:11:12 -04:00
Dan Mulloy 4be2bf38ff Switch from travis-ci.org to .com 2021-06-18 10:53:17 -04:00
Pasqual Koschmieder 0a32f24f08 Fix EntityUseAction & Hand read for minecraft 1.17 (#1230) 2021-06-18 10:47:48 -04:00
Dan Mulloy 1c2bc274dd Fix differently mapped fastutil classes in Paper
Fixes #1228
2021-06-16 18:22:53 -04:00
Dan Mulloy 9b54794f6b Fix cloning in 1.17
Addresses #1222
2021-06-15 23:58:40 -04:00
Dan Mulloy 42bec5a858 Fix entity trackers in 1.17
Addresses #1217
2021-06-15 18:39:34 -04:00
Dan Mulloy fa317c1167 Working on live server 2021-06-14 16:36:22 -04:00
Dan Mulloy cc17b9ee6e Finish up unit tests 2021-06-13 17:18:36 -04:00
Dan Mulloy 190ca1ff6a Almost there, /theoretically/ 2021-06-13 15:57:23 -04:00
Dan Mulloy c51930121f Fix a bunch more classpaths
Also worked on fixing some wrappers
2021-06-13 11:36:44 -04:00
Dan Mulloy c7a8d734d4 Fix some more class paths 2021-06-12 23:53:09 -04:00
Dan Mulloy b446cf2183 Update unit tests to 1.17, fix packet lookups 2021-06-12 15:28:01 -04:00
Dan Mulloy f11c246276 Deprecate SocketInjector#getSocket (see #1188) 2021-06-04 18:24:30 -04:00
Camotoy 6f91bd23de Remove inferences of SocketChannel presence in temporary player (#1188)
To note: this is yet another compatibility change for my Geyser work, but https://github.com/PaperMC/Paper/pull/5611 will also break without these changes as Unix domain sockets don't implement SocketChannel.

The temporary player method delegation directed the isOnline and getName methods to functions that require the channel to be an instance of SocketChannel, when this won't always be the case. To solve this, this PR redirects `getSocket().getRemoteSocketAddress()` to `injector.getAddress()` which returns the same value. To determine if the player is online, a new method is created in SocketInjector to determine if a connection is online (which also returns the same value as before this commit).
2021-06-04 18:21:48 -04:00
Pim van der Loos 45c293df7d Update ByteBuddy dependency (#1156)
- Updated ByteBuddy dependency to add support for Java 17 (introduced in 1.10.21) and to take advantage of any fixes and improvements made between versions.
2021-05-29 13:01:45 -04:00
Camotoy 5acdb2b3c5 Mark connectionHandler as sharable (#1179)
Fixes #1170.

The reasoning for this PR is largely outlined in the above issue. As the ChannelInboundHandlerAdapter has no private class, a race condition cannot occur and therefore marking the class as sharable should incur no cost.
2021-05-29 13:00:58 -04:00
Camotoy 72c1f3e26c Redirect #mcdevs link to libera.chat (#1181)
The #mcdevs IRC channel is in the process of migrating away from Freenode.
2021-05-29 12:59:26 -04:00
Minecrell b3ccf82597 WrappedServerPing: Properly translate MotD to components for RGB colors (#1152)
In previous Minecraft versions, using WrappedServerPing.setMotD(String)
behaved exactly like using Bukkit's ServerListPingEvent.setMotd(String).

With the addition of RGB colors in Minecraft 1.16, Spigot's
ServerListPingEvent was patched to translate the MotD string to the
chat component equivalent to make it possible to use RGB colors in MotDs.
In general, using raw legacy color codes (e.g. §c) within a (JSON) text
component tends to cause weird issues on newer Minecraft versions,
so it's better to translate them to the JSON equivalents on the server.

However, the WrappedServerPing implementation in ProtocolLib was never
updated with the same change, which makes it behave differently from
Spigot's ServerListPingEvent now. Using ServerListPingEvent RGB color
codes work, using ProtocolLib they do not work.

To fix this, this commit changes WrappedServerPing.setMotD(String) to
use the same method as Spigot for translating the legacy text to the
JSON/chat component equivalent.

This allows for example ServerListPlus to use Spigot's RGB color codes
(e.g. &x&7&9&b&8&f&fHello) without requiring any changes in ServerListPlus.
2021-05-02 17:43:32 -04:00
LewUwU 0c01a11755 Fixed protocol injection with latest netty on minecraft 1.11 and below (#1067) 2021-05-01 15:51:52 -04:00
Pim van der Loos 7ce3f471bf Add support for Java 16 (#1120)
Switched from the now-unavailable ClassLoader::defineClass method to the java.lang.invoke.MethodHandles.Lookup::defineClass. This is available on Java 9+.
2021-04-04 23:03:05 -04:00
Dan Mulloy 97972acee8 Add converter for game state ids
Fixes #1041
2021-02-27 15:38:05 -05:00
LOOHP a0bb11e1bd Add support for PaperMC's new Adventure API Component (#1103) 2021-02-27 14:15:43 -05:00
Dan Mulloy ba74fceed6 Update version to 4.6.1-SNAPSHOT 2021-02-27 14:15:01 -05:00
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
333 changed files with 23030 additions and 34569 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+2 -2
View File
@@ -19,7 +19,7 @@ This page will detail specific things that must be done if you intend to contrib
#### Ready? Let's get started!
1. Read the [Protocol Changes](http://wiki.vg/Protocol_History). Always make sure the list is both
complete and correct. If you're unsure, don't hesitate to ask in #mcdevs (the people who maintain wiki.vg) on [freenode.net](http://freenode.net)
complete and correct. If you're unsure, don't hesitate to ask in #mcdevs (the people who maintain wiki.vg) on [libera.chat](http://libera.chat)
or #spigot on [irc.spi.gt](http://irc.spi.gt) ([webchat](https://irc.spi.gt/iris/?channels=spigot)).
2. Search for usages of the now-defunct NMS package guard and change them.
3. The class `com.comphenix.protocol.PacketType` contains a list of all the packets. If any packets were added or removed
@@ -35,4 +35,4 @@ and the release date.
8. `com.comphenix.protocol.utility.MinecraftProtocolVersion` contains a map of all the protocol version integers.
If the protocol version has been incremented, add a new line to the map.
9. `mvn` in root directory again. If it builds successfully, test on the appropriate version of a Spigot server. If
the build fails, debug!
the build fails, debug!
+2
View File
@@ -0,0 +1,2 @@
github: [dmulloy2]
custom: ["https://paypal.me/dmulloy2"]
+1
View File
@@ -2,6 +2,7 @@ _Follow this template except for feature requests. Use pastebin when providing /
Make sure you've done the following:
- [ ] You're using the latest build for your server version
- [ ] This isn't fixed in a recent development build
- [ ] This isn't an issue caused by another plugin
- [ ] You've checked for duplicate issues
- [ ] You didn't use `/reload`
+2
View File
@@ -7,6 +7,8 @@ assignees: ''
---
- [ ] This issue is not solved in a development build
**Describe the bug**
A clear and concise description of what the bug is.
@@ -7,6 +7,8 @@ assignees: ''
---
- [ ] This feature is not currently present in a development build
**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 [...]
+28
View File
@@ -0,0 +1,28 @@
name: ProtocolLib full build lifecycle
on:
- push
- pull_request
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
- name: Run maven build lifecycle
run: mvn --batch-mode clean test package
- name: Upload plugin file
uses: actions/upload-artifact@v2
with:
name: ProtocolLib
path: target/ProtocolLib.jar
+41
View File
@@ -0,0 +1,41 @@
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '28 9 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: 'java'
- name: Run maven build lifecycle
run: mvn --batch-mode clean test package
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+2 -1
View File
@@ -179,4 +179,5 @@ logs/
.gradle/
build/
logs/
logs/
.java-version
-11
View File
@@ -1,11 +0,0 @@
language: java
jdk:
- openjdk8
script: mvn clean test
install: true
notifications:
email:
recipients:
- "dmulloy2@live.com"
on_success: change
on_failure: always
+167 -169
View File
@@ -1,169 +1,167 @@
# ProtocolLib [![Travis Status](https://travis-ci.org/dmulloy2/ProtocolLib.svg?branch=master)](https://travis-ci.org/dmulloy2/ProtocolLib)
Certain tasks are impossible to perform with the standard Bukkit API, and may require
working with and even modify Minecraft directly. A common technique is to modify incoming
and outgoing [packets](https://www.wiki.vg/Protocol), or inject custom packets into the
stream. This is quite cumbersome to do, however, and most implementations will break
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
Critically, different plugins that use this approach may _hook_ into the same classes,
with unpredictable outcomes. More than often this causes plugins to crash, but it may also
lead to more subtle bugs.
Currently maintained by dmulloy2 on behalf of [Spigot](https://www.spigotmc.org/).
### Resources
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib)
* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc)
### Compilation
ProtocolLib is built with Maven and requires Spigot and SpigotAPI, which can be found [here](https://www.spigotmc.org/wiki/buildtools/).
### A new API
__ProtocolLib__ attempts to solve this problem by providing a event API, much like Bukkit,
that allows plugins to monitor, modify, or cancel packets sent and received. But, more importantly,
the API also hides all the gritty, obfuscated classes with a simple index based read/write system.
You no longer have to reference CraftBukkit!
### Using ProtocolLib
To use this library, first add ProtocolLib.jar to your Java build path. Then, add ProtocolLib
as a dependency or soft dependency to your plugin.yml file like any other plugin:
````yml
depend: [ProtocolLib]
````
You can also add ProtocolLib as a Maven dependency:
````xml
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
</repository>
...
</repositories>
<dependencies>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.5.0</version>
</dependency>
</dependencies>
````
Or use the maven dependency with gradle:
```gradle
repositories {
maven { url "https://repo.dmulloy2.net/nexus/repository/public/" }
}
dependencies {
compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.5.0";
}
```
Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go.
````java
private ProtocolManager protocolManager;
public void onLoad() {
protocolManager = ProtocolLibrary.getProtocolManager();
}
````
To listen for packets sent by the server to a client, add a server-side listener:
````java
// Disable all sound effects
protocolManager.addPacketListener(
new PacketAdapter(this, ListenerPriority.NORMAL,
PacketType.Play.Server.NAMED_SOUND_EFFECT) {
@Override
public void onPacketSending(PacketEvent event) {
// Item packets (id: 0x29)
if (event.getPacketType() ==
PacketType.Play.Server.NAMED_SOUND_EFFECT) {
event.setCancelled(true);
}
}
});
````
It's also possible to read and modify the content of these packets. For instance, you can create a global
censor by listening for Packet3Chat events:
````java
// Censor
protocolManager.addPacketListener(new PacketAdapter(this,
ListenerPriority.NORMAL,
PacketType.Play.Client.CHAT) {
@Override
public void onPacketReceiving(PacketEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CHAT) {
PacketContainer packet = event.getPacket();
String message = packet.getStrings().read(0);
if (message.contains("shit")
|| message.contains("damn")) {
event.setCancelled(true);
event.getPlayer().sendMessage("Bad manners!");
}
}
}
});
````
### Sending packets
Normally, you might have to do something ugly like the following:
````java
Packet60Explosion fakeExplosion = new Packet60Explosion();
fakeExplosion.a = player.getLocation().getX();
fakeExplosion.b = player.getLocation().getY();
fakeExplosion.c = player.getLocation().getZ();
fakeExplosion.d = 3.0F;
fakeExplosion.e = new ArrayList<Object>();
((CraftPlayer) player).getHandle().netServerHandler.sendPacket(fakeExplosion);
````
But with ProtocolLib, you can turn that into something more manageable. Notice that
you don't have to create an ArrayList with this version:
````java
PacketContainer fakeExplosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
fakeExplosion.getDoubles().
write(0, player.getLocation().getX()).
write(1, player.getLocation().getY()).
write(2, player.getLocation().getZ());
fakeExplosion.getFloat().write(0, 3.0F);
try {
protocolManager.sendServerPacket(player, fakeExplosion);
} catch (InvocationTargetException e) {
throw new RuntimeException(
"Cannot send packet " + fakeExplosion, e);
}
````
### Compatibility
One of the main goals of this project was to achieve maximum compatibility with CraftBukkit. And the end
result is quite flexible. Aside from netty package changes, it should be resilient against future changes.
It's likely that I won't have to update ProtocolLib for anything but bug fixes and new features.
How is this possible? It all comes down to reflection in the end. Essentially, no name is hard coded -
every field, method and class is deduced by looking at field types, package names or parameter
types. It's remarkably consistent across different versions.
# ProtocolLib
Certain tasks are impossible to perform with the standard Bukkit API, and may require
working with and even modifying 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/index.html)
### Compilation
ProtocolLib is built with [Maven](https://maven.apache.org/). If you have it installed, just run
`mvn package` in the root project folder.
### A new API
__ProtocolLib__ attempts to solve this problem by providing an 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.7.0</version>
<scope>provided</scope>
</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.7.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) {
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) {
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
PacketPlayOutExplosion fakeExplosion = new PacketPlayOutExplosion(
player.getLocation().getX(),
player.getLocation().getY(),
player.getLocation().getZ(),
3.0F,
new ArrayList<>(),
new Vec3D(
player.getVelocity().getX() + 1,
player.getVelocity().getY() + 1,
player.getVelocity().getZ() + 1
)
);
((CraftPlayer) player).getHandle().b.a(fakeExplosion);
````
But with ProtocolLib, you can turn that into something more manageable:
````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);
fakeExplosion.getBlockPositionCollectionModifier().write(0, new ArrayList<>());
fakeExplosion.getVectors().write(0, player.getVelocity().add(new Vector(1, 1, 1)));
protocolManager.sendServerPacket(player, fakeExplosion);
````
### Compatibility
One of the main goals of this project was to achieve maximum compatibility with CraftBukkit. And the end
result is quite flexible. 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.
+1 -1
View File
@@ -25,7 +25,7 @@
</repository>
<repository>
<id>dmulloy2-repo</id>
<url>http://repo.dmulloy2.net/content/groups/public/</url>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
</repositories>
@@ -72,7 +72,7 @@ public abstract class TinyProtocol {
private List<Object> networkManagers;
// Injected channel handlers
private List<Channel> serverChannels = Lists.newArrayList();
private List<Channel> serverChannels = new ArrayList<>();
private ChannelInboundHandlerAdapter serverChannelHandler;
private ChannelInitializer<Channel> beginInitProtocol;
private ChannelInitializer<Channel> endInitProtocol;
+5
View File
@@ -0,0 +1,5 @@
before_install:
- source "$HOME/.sdkman/bin/sdkman-init.sh"
- sdk update
- sdk install java 17.0.4-tem
- sdk use java 17.0.4-tem
+94 -50
View File
@@ -1,10 +1,10 @@
<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>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name>
<version>4.5.0</version>
<version>5.0.0-SNAPSHOT</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>https://github.com/dmulloy2/ProtocolLib</url>
@@ -16,8 +16,10 @@
<project.build.number></project.build.number>
<project.fullVersion>${project.version}</project.fullVersion>
<powermock.version>2.0.4</powermock.version>
<spigot.version>1.15.1-R0.1-SNAPSHOT</spigot.version>
<junit.version>5.9.0</junit.version>
<mockito.version>4.6.1</mockito.version>
<netty.version>4.1.77.Final</netty.version>
<spigot.version>1.19.3-R0.1-SNAPSHOT</spigot.version>
</properties>
<build>
@@ -38,7 +40,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
@@ -51,8 +53,8 @@
<relocations>
<relocation>
<pattern>net.sf</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern>
<pattern>net.bytebuddy</pattern>
<shadedPattern>com.comphenix.net.bytebuddy</shadedPattern>
</relocation>
</relocations>
@@ -71,7 +73,7 @@
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -81,7 +83,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<version>3.2.2</version>
<configuration>
<archive>
<manifestEntries>
@@ -98,27 +100,40 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M4</version>
<version>3.0.0-M7</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
<configuration>
<trimStackTrace>false</trimStackTrace>
<useFile>false</useFile>
<systemProperties>
<property>
<name>projectVersion</name>
<value>${project.version}</value>
</property>
</systemProperties>
<!-- We need this as we use reflection in tests to ensure equality -->
<argLine>--add-opens java.base/java.util=ALL-UNNAMED</argLine>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.1</version>
<version>3.4.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>
Comphenix and dmulloy2. Licensed under the GNU GPL v2.
</bottom>
<additionalOptions>
<additionalOption>-Xdoclint:none</additionalOption>
</additionalOptions>
@@ -145,8 +160,8 @@
</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>
<project.fullVersion>${project.version}${project.build.number}</project.fullVersion>
<!--<project.fullVersion>${project.version}</project.fullVersion>-->
</properties>
</profile>
@@ -166,7 +181,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
@@ -180,7 +195,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.1</version>
<version>3.4.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
@@ -222,7 +237,7 @@
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
<url>https://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
@@ -231,7 +246,7 @@
<developer>
<id>dmulloy2</id>
<name>Dan Mulloy</name>
<url>http://dmulloy2.net/</url>
<url>https://dmulloy2.net/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
@@ -241,7 +256,7 @@
<id>aadnk</id>
<name>Kristian S. Stangeland</name>
<email>kr_stang@hotmail.com</email>
<url>http://comphenix.net/</url>
<url>https://comphenix.net/</url>
<roles>
<role>former author</role>
</roles>
@@ -252,18 +267,18 @@
<distributionManagement>
<repository>
<id>dmulloy2-releases</id>
<url>https://repo.dmulloy2.net/nexus/repository/releases/</url>
<url>https://repo.dmulloy2.net/repository/releases/</url>
</repository>
<snapshotRepository>
<id>dmulloy2-snapshots</id>
<url>https://repo.dmulloy2.net/nexus/repository/snapshots/</url>
<url>https://repo.dmulloy2.net/repository/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
<repository>
<id>spigot-repo</id>
@@ -272,63 +287,92 @@
</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>
<!-- core minecraft dependencies -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.5</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- TODO fold BukkitExecutors into ProtocolLib -->
<!-- Compile with the old version of Netty for backwards compat -->
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>com.comphenix.executors</groupId>
<artifactId>BukkitExecutors</artifactId>
<version>1.1-SNAPSHOT</version>
<scope>compile</scope>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.23.Final</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/net.kyori/adventure-text-serializer-gson -->
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
<version>4.11.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.12.12</version>
</dependency>
<!-- Testing dependencies -->
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.2.0</version>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-common -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>${netty.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-transport -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${netty.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -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();
}
@@ -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]);
}
}
@@ -41,9 +41,9 @@ abstract class CommandBase implements CommandExecutor {
public static final String PERMISSION_ADMIN = "protocol.admin";
private String permission;
private String name;
private int minimumArgumentCount;
private final String permission;
private final String name;
private final int minimumArgumentCount;
protected ErrorReporter reporter;
@@ -97,18 +97,23 @@ abstract class CommandBase implements CommandExecutor {
Boolean result = null;
if (!arguments.isEmpty()) {
String arg = arguments.peek();
if (arg.equalsIgnoreCase("true") || arg.equalsIgnoreCase("on"))
result = true;
else if (arg.equalsIgnoreCase(parameterName))
result = true;
else if (arg.equalsIgnoreCase("false") || arg.equalsIgnoreCase("off"))
result = false;
final String arg = arguments.peek().toLowerCase();
switch (arg) {
case "true":
case "on":
result = true;
break;
case "false":
case "off":
result = false;
break;
default:
if (arg.equalsIgnoreCase(parameterName)) result = true;
break;
}
}
if (result != null)
arguments.poll();
if (result != null) arguments.poll();
return result;
}
@@ -119,7 +124,7 @@ abstract class CommandBase implements CommandExecutor {
* @return A queue that contains every element in the array, starting at the given index.
*/
protected Deque<String> toQueue(String[] args, int start) {
return new ArrayDeque<String>(Arrays.asList(args).subList(start, args.length));
return new ArrayDeque<>(Arrays.asList(args).subList(start, args.length));
}
/**
@@ -2,6 +2,7 @@ package com.comphenix.protocol;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -26,7 +27,6 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.Sets;
/**
* A command to apply JavaScript filtering to the packet command.
@@ -79,7 +79,7 @@ public class CommandFilter extends CommandBase {
public Filter(String name, String predicate, Set<PacketType> packets) {
this.name = name;
this.predicate = predicate;
this.packets = Sets.newHashSet(packets);
this.packets = new HashSet<>(packets);
}
/**
@@ -103,7 +103,7 @@ public class CommandFilter extends CommandBase {
* @return Set of packets this filter applies to.
*/
public Set<PacketType> getRanges() {
return Sets.newHashSet(packets);
return new HashSet<>(packets);
}
/**
@@ -22,6 +22,7 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -29,8 +30,6 @@ 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;
@@ -42,18 +41,11 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.PrettyPrinter;
import com.comphenix.protocol.reflect.PrettyPrinter.ObjectPrinter;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.HexDumper;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;
/**
* Handles the "packet" debug command.
@@ -77,11 +69,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;
@@ -127,13 +114,7 @@ class CommandPacket extends CommandBase {
* @return TRUE if the message was sent successfully, FALSE otherwise.
*/
public void sendMessageSilently(CommandSender receiver, String message) {
try {
chatter.sendMessageSilently(receiver, message);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(receiver, message)
);
}
chatter.sendMessageSilently(receiver, message);
}
/**
@@ -142,13 +123,7 @@ class CommandPacket extends CommandBase {
* @param permission - permission required to receieve the message. NULL to target everyone.
*/
public void broadcastMessageSilently(String message, String permission) {
try {
chatter.broadcastMessageSilently(message, permission);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(message, permission)
);
}
chatter.broadcastMessageSilently(message, permission);
}
private void printPage(CommandSender sender, int pageIndex) {
@@ -312,7 +287,7 @@ class CommandPacket extends CommandBase {
}
private Set<PacketType> filterTypes(Set<PacketType> types, Sender sender) {
Set<PacketType> result = Sets.newHashSet();
final Set<PacketType> result = new HashSet<>();
for (PacketType type : types) {
if (type.getSender() == sender) {
@@ -371,7 +346,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 +403,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 +425,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) {
@@ -529,19 +442,21 @@ class CommandPacket extends CommandBase {
return listener;
}
private SubCommand parseCommand(Deque<String> arguments) {
String text = arguments.poll().toLowerCase();
// Parse this too
if ("add".startsWith(text))
return SubCommand.ADD;
else if ("remove".startsWith(text))
return SubCommand.REMOVE;
else if ("names".startsWith(text))
return SubCommand.NAMES;
else if ("page".startsWith(text))
return SubCommand.PAGE;
else
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.");
private SubCommand parseCommand(Deque<String> arguments)
{
final String text = arguments.remove().toLowerCase();
switch (arguments.remove().toLowerCase()) {
case "add":
return SubCommand.ADD;
case "remove":
return SubCommand.REMOVE;
case "names":
return SubCommand.NAMES;
case "page":
return SubCommand.PAGE;
default:
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.");
}
}
}
@@ -218,14 +218,13 @@ class CommandProtocol extends CommandBase {
private static SimpleDateFormat TIMESTAMP_FORMAT;
private void dump(CommandSender sender) {
Closer closer = Closer.create();
if (FILE_FORMAT == null)
FILE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
if (TIMESTAMP_FORMAT == null)
TIMESTAMP_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss");
try {
try (Closer closer = Closer.create()) {
Date date = new Date();
File file = new File(plugin.getDataFolder(), "dump-" + FILE_FORMAT.format(date) + ".txt");
if (file.exists()) {
@@ -233,7 +232,7 @@ class CommandProtocol extends CommandBase {
}
file.createNewFile();
FileWriter fw = closer.register(new FileWriter(file));
PrintWriter pw = closer.register(new PrintWriter(fw));
@@ -285,8 +284,6 @@ class CommandProtocol extends CommandBase {
} catch (IOException ex) {
ProtocolLogger.log(Level.SEVERE, "Failed to create dump:", ex);
sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!");
} finally {
closer.close();
}
}
}
@@ -1,268 +1,284 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2017 Dan Mulloy
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.*;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.google.common.base.Charsets;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.libs.org.apache.commons.io.HexDump;
import org.bukkit.plugin.Plugin;
/**
* Logs packets to a given stream
* @author dmulloy2
*/
public class PacketLogging implements CommandExecutor, PacketListener {
public static final String NAME = "packetlog";
private List<PacketType> sendingTypes = new ArrayList<>();
private List<PacketType> receivingTypes = new ArrayList<>();
private ListeningWhitelist sendingWhitelist;
private ListeningWhitelist receivingWhitelist;
private Logger fileLogger;
private LogLocation location = LogLocation.FILE;
private final ProtocolManager manager;
private final Plugin plugin;
PacketLogging(Plugin plugin, ProtocolManager manager) {
this.plugin = plugin;
this.manager = manager;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
PacketType type = null;
try {
if (args.length > 2) {
Protocol protocol;
try {
protocol = Protocol.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
return true;
}
Sender pSender;
try {
pSender = Sender.valueOf(args[1].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
return true;
}
try {
try { // Try IDs first
int id = Integer.parseInt(args[2]);
type = PacketType.findCurrent(protocol, pSender, id);
} catch (NumberFormatException ex) { // Check packet names
String name = args[2];
for (PacketType packet : PacketType.values()) {
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
if (packet.name().equalsIgnoreCase(name)) {
type = packet;
break;
}
for (String className : packet.getClassNames()) {
if (className.equalsIgnoreCase(name)) {
type = packet;
break;
}
}
}
}
}
} catch (IllegalArgumentException ex) { // RIP
type = null;
}
if (type == null) {
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
return true;
}
if (args.length > 3) {
if (args[3].equalsIgnoreCase("console")) {
this.location = LogLocation.CONSOLE;
} else {
this.location = LogLocation.FILE;
}
}
if (pSender == Sender.CLIENT) {
if (receivingTypes.contains(type)) {
receivingTypes.remove(type);
} else {
receivingTypes.add(type);
}
} else {
if (sendingTypes.contains(type)) {
sendingTypes.remove(type);
} else {
sendingTypes.add(type);
}
}
startLogging();
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
return true;
}
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
return true;
} catch (Throwable ex) {
sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString());
return true;
}
}
private void startLogging() {
manager.removePacketListener(this);
if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
return;
}
this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();
// Setup the file logger if it hasn't been already
if (location == LogLocation.FILE && fileLogger == null) {
fileLogger = Logger.getLogger("ProtocolLib-FileLogging");
for (Handler handler : fileLogger.getHandlers())
fileLogger.removeHandler(handler);
fileLogger.setUseParentHandlers(false);
try {
File logFile = new File(plugin.getDataFolder(), "log.log");
FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
handler.setFormatter(new LogFormatter());
fileLogger.addHandler(handler);
} catch (IOException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
return;
}
}
manager.addPacketListener(this);
}
@Override
public void onPacketSending(PacketEvent event) {
log(event);
}
@Override
public void onPacketReceiving(PacketEvent event) {
log(event);
}
// Here's where the magic happens
private static String hexDump(byte[] bytes) throws IOException {
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
HexDump.dump(bytes, 0, output, 0);
return new String(output.toByteArray(), Charsets.UTF_8);
}
}
private void log(PacketEvent event) {
try {
byte[] bytes = WirePacket.bytesFromPacket(event.getPacket());
String hexDump = hexDump(bytes);
if (location == LogLocation.FILE) {
fileLogger.log(Level.INFO, event.getPacketType() + ":");
fileLogger.log(Level.INFO, hexDump);
fileLogger.log(Level.INFO, "");
} else {
System.out.println(event.getPacketType() + ":");
System.out.println(hexDump);
System.out.println();
}
} catch (Throwable ex) {
plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex);
plugin.getLogger().log(Level.WARNING, "Clearing packet logger...");
sendingTypes.clear();
receivingTypes.clear();
startLogging();
}
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
private enum LogLocation {
CONSOLE, FILE
}
private static class LogFormatter extends Formatter {
private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final String FORMAT = "[{0}] {1}";
@Override
public String format(LogRecord record) {
String string = formatMessage(record);
if (string.isEmpty()) {
return LINE_SEPARATOR;
}
StringBuilder message = new StringBuilder();
message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string));
message.append(LINE_SEPARATOR);
return message.toString();
}
}
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2017 Dan Mulloy
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
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.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
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;
/**
* Logs packets to a given stream
* @author dmulloy2
*/
public class PacketLogging implements CommandExecutor, PacketListener {
public static final String NAME = "packetlog";
private static MethodAccessor HEX_DUMP;
private List<PacketType> sendingTypes = new ArrayList<>();
private List<PacketType> receivingTypes = new ArrayList<>();
private ListeningWhitelist sendingWhitelist;
private ListeningWhitelist receivingWhitelist;
private Logger fileLogger;
private LogLocation location = LogLocation.FILE;
private final ProtocolManager manager;
private final Plugin plugin;
PacketLogging(Plugin plugin, ProtocolManager manager) {
this.plugin = plugin;
this.manager = manager;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
PacketType type = null;
try {
if (args.length > 2) {
Protocol protocol;
try {
protocol = Protocol.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
return true;
}
Sender pSender;
try {
pSender = Sender.valueOf(args[1].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
return true;
}
try {
try { // Try IDs first
int id = Integer.parseInt(args[2]);
type = PacketType.findCurrent(protocol, pSender, id);
} catch (NumberFormatException ex) { // Check packet names
String name = args[2];
for (PacketType packet : PacketType.values()) {
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
if (packet.name().equalsIgnoreCase(name)) {
type = packet;
break;
}
for (String className : packet.getClassNames()) {
if (className.equalsIgnoreCase(name)) {
type = packet;
break;
}
}
}
}
}
} catch (IllegalArgumentException ex) { // RIP
type = null;
}
if (type == null) {
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
return true;
}
if (args.length > 3) {
if (args[3].equalsIgnoreCase("console")) {
this.location = LogLocation.CONSOLE;
} else {
this.location = LogLocation.FILE;
}
}
if (pSender == Sender.CLIENT) {
if (receivingTypes.contains(type)) {
receivingTypes.remove(type);
} else {
receivingTypes.add(type);
}
} else {
if (sendingTypes.contains(type)) {
sendingTypes.remove(type);
} else {
sendingTypes.add(type);
}
}
startLogging();
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
return true;
}
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
return true;
} catch (Throwable ex) {
sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString());
return true;
}
}
private void startLogging() {
manager.removePacketListener(this);
if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
return;
}
this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();
// Setup the file logger if it hasn't been already
if (location == LogLocation.FILE && fileLogger == null) {
fileLogger = Logger.getLogger("ProtocolLib-FileLogging");
for (Handler handler : fileLogger.getHandlers())
fileLogger.removeHandler(handler);
fileLogger.setUseParentHandlers(false);
try {
File logFile = new File(plugin.getDataFolder(), "log.log");
FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
handler.setFormatter(new LogFormatter());
fileLogger.addHandler(handler);
} catch (IOException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
return;
}
}
manager.addPacketListener(this);
}
@Override
public void onPacketSending(PacketEvent event) {
log(event);
}
@Override
public void onPacketReceiving(PacketEvent event) {
log(event);
}
// Here's where the magic happens
private static String hexDump(byte[] bytes) throws IOException {
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
if (HEX_DUMP == null) {
Class<?> hexDumpClass = MinecraftReflection.getLibraryClass("org.apache.commons.io.HexDump");
HEX_DUMP = Accessors.getMethodAccessor(FuzzyReflection.fromClass(hexDumpClass)
.getMethodByParameters("dump", byte[].class, long.class, OutputStream.class, int.class));
}
HEX_DUMP.invoke(null, bytes, 0, output, 0);
return new String(output.toByteArray(), StandardCharsets.UTF_8);
}
}
private void log(PacketEvent event) {
try {
byte[] bytes = WirePacket.bytesFromPacket(event.getPacket());
String hexDump = hexDump(bytes);
if (location == LogLocation.FILE) {
fileLogger.log(Level.INFO, event.getPacketType() + ":");
fileLogger.log(Level.INFO, hexDump);
fileLogger.log(Level.INFO, "");
} else {
System.out.println(event.getPacketType() + ":");
System.out.println(hexDump);
System.out.println();
}
} catch (Throwable ex) {
plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex);
plugin.getLogger().log(Level.WARNING, "Clearing packet logger...");
sendingTypes.clear();
receivingTypes.clear();
startLogging();
}
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
private enum LogLocation {
CONSOLE, FILE
}
private static class LogFormatter extends Formatter {
private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final String FORMAT = "[{0}] {1}";
@Override
public String format(LogRecord record) {
String string = formatMessage(record);
if (string.isEmpty()) {
return LINE_SEPARATOR;
}
StringBuilder message = new StringBuilder();
message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string));
message.append(LINE_SEPARATOR);
return message.toString();
}
}
}
@@ -17,98 +17,87 @@
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.netty.WirePacket;
import org.bukkit.entity.Player;
/**
* Represents a object capable of sending or receiving packets.
*
*
* @author Kristian
*/
public interface PacketStream {
/**
* Send a packet to the given player.
* @param receiver - the reciever.
* @param packet - packet to send.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player receiver, PacketContainer packet)
throws InvocationTargetException;
/**
* Send a packet to the given player.
*
* @param receiver - the reciever.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occured when sending the packet.
* @param packet - packet to send.
*/
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
throws InvocationTargetException;
void sendServerPacket(Player receiver, PacketContainer packet);
/**
* Send a packet to the given player.
* @param receiver - the receiver.
* @param packet - packet to send.
* @param marker - the network marker to use.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occured when sending the packet.
*
* @param receiver - the reciever.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
*/
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters)
throws InvocationTargetException;
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters);
/**
* Send a packet to the given player.
*
* @param receiver - the receiver.
* @param packet - packet to send.
* @param marker - the network marker to use.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
*/
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters);
/**
* Send a wire packet to the given player.
*
* @param receiver - the receiver.
* @param id - packet id.
* @param bytes - packet bytes.
* @throws InvocationTargetException if an error occured when sending the packet.
* @param id - packet id.
* @param bytes - packet bytes.
*/
public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException;
void sendWirePacket(Player receiver, int id, byte[] bytes);
/**
* Send a wire packet to the given player.
*
* @param receiver - the receiver.
* @param packet - packet to send.
* @throws InvocationTargetException if an error occured when sending the packet.
* @param packet - packet to send.
*/
public void sendWirePacket(Player receiver, WirePacket packet) throws InvocationTargetException;
void sendWirePacket(Player receiver, WirePacket packet);
/**
* Simulate recieving a certain packet from a given player.
*
* @param sender - the sender.
* @param packet - the packet that was sent.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet)
throws IllegalAccessException, InvocationTargetException;
void receiveClientPacket(Player sender, PacketContainer packet);
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
*
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
throws IllegalAccessException, InvocationTargetException;
void receiveClientPacket(Player sender, PacketContainer packet, boolean filters);
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param marker - the network marker to use.
*
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param marker - the network marker to use.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters)
throws IllegalAccessException, InvocationTargetException;
void receiveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters);
}
File diff suppressed because it is too large. Load diff
@@ -23,11 +23,6 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Sets;
/**
* Represents a more modern object-based enum.
* <p>
@@ -37,7 +32,7 @@ import com.google.common.collect.Sets;
*/
public class PacketTypeEnum implements Iterable<PacketType> {
// Used to convert between IDs and names
protected Set<PacketType> members = Sets.newHashSet();
protected Set<PacketType> members = new HashSet<>();
/**
* Registers every declared PacketType field.
@@ -110,22 +105,6 @@ public class PacketTypeEnum implements Iterable<PacketType> {
public boolean hasMember(PacketType member) {
return members.contains(member);
}
/**
* Retrieve a member by name,
* @param name - name of member to retrieve.
* @return The member, or NULL if not found.
* @deprecated Don't use this
*/
@Deprecated
public PacketType valueOf(String name) {
for (PacketType member : members) {
if (member.name().equals(name))
return member;
}
return null;
}
/**
* Retrieve every registered member.
@@ -19,14 +19,14 @@ import com.google.common.collect.Multimap;
class PacketTypeLookup {
public static class ProtocolSenderLookup {
// Unroll lookup for performance reasons
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> HANDSHAKE_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> GAME_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> GAME_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> STATUS_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> STATUS_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> LOGIN_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> LOGIN_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = new IntegerMap<>();
public final IntegerMap<PacketType> HANDSHAKE_SERVER = new IntegerMap<>();
public final IntegerMap<PacketType> GAME_CLIENT = new IntegerMap<>();
public final IntegerMap<PacketType> GAME_SERVER = new IntegerMap<>();
public final IntegerMap<PacketType> STATUS_CLIENT = new IntegerMap<>();
public final IntegerMap<PacketType> STATUS_SERVER = new IntegerMap<>();
public final IntegerMap<PacketType> LOGIN_CLIENT = new IntegerMap<>();
public final IntegerMap<PacketType> LOGIN_SERVER = new IntegerMap<>();
/**
* Retrieve the correct integer map for a specific protocol and sender.
@@ -52,14 +52,14 @@ class PacketTypeLookup {
public static class ClassLookup {
// Unroll lookup for performance reasons
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<>();
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<>();
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<>();
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<>();
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<>();
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<>();
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<>();
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<>();
/**
* Retrieve the correct integer map for a specific protocol and sender.
@@ -84,9 +84,9 @@ class PacketTypeLookup {
}
// Packet IDs from 1.6.4 and below
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> serverLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> clientLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<>();
private final IntegerMap<PacketType> serverLookup = new IntegerMap<>();
private final IntegerMap<PacketType> clientLookup = new IntegerMap<>();
// Packets for 1.7.2
private final ProtocolSenderLookup idLookup = new ProtocolSenderLookup();
@@ -105,20 +105,10 @@ 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);
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames()[0], type);
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames().get(0), type);
}
nameLookup.put(type.name(), type);
}
@@ -1,29 +1,29 @@
package com.comphenix.protocol;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.ConnectionSide;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
class PacketTypeParser {
public final static Range<Integer> DEFAULT_MAX_RANGE = Range.closed(0, 255);
public static final Range<Integer> DEFAULT_MAX_RANGE = Range.closed(0, 255);
private Sender side = null;
private Protocol protocol = null;
public Set<PacketType> parseTypes(Deque<String> arguments, Range<Integer> defaultRange) {
Set<PacketType> result = Sets.newHashSet();
final Set<PacketType> result = new HashSet<>();
side = null;
protocol = null;
@@ -68,8 +68,7 @@ class PacketTypeParser {
// Supply a default integer range
if (ranges.isEmpty() && result.isEmpty()) {
ranges = Lists.newArrayList();
ranges.add(defaultRange);
ranges = Collections.singletonList(defaultRange);
}
for (Range<Integer> range : ranges) {
@@ -130,19 +129,20 @@ class PacketTypeParser {
* @return The protocol, or NULL if not found.
*/
public Protocol parseProtocol(String text) {
if (text == null)
return null;
String candidate = text.toLowerCase();
if ("handshake".equals(candidate) || "handshaking".equals(candidate))
return Protocol.HANDSHAKING;
else if ("login".equals(candidate))
return Protocol.LOGIN;
else if ("play".equals(candidate) || "game".equals(candidate))
return Protocol.PLAY;
else if ("status".equals(candidate))
return Protocol.STATUS;
else
return null;
if (text == null) return null;
switch (text.toLowerCase()) {
case "handshake":
case "handshaking":
return Protocol.HANDSHAKING;
case "login":
return Protocol.LOGIN;
case "play":
case "game":
return Protocol.PLAY;
case "status":
return Protocol.STATUS;
default: return null;
}
}
}
@@ -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,43 +1,38 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* 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
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2012 Kristian S.
* Stangeland
* <p>
* 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.
* <p>
* 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.
* <p>
* You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.comphenix.protocol;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
/**
* Represents the configuration of ProtocolLib.
*
*
* @author Kristian
*/
public class ProtocolConfig {
private static final String LAST_UPDATE_FILE = "lastupdate";
private static final String SECTION_GLOBAL = "global";
@@ -46,11 +41,10 @@ public class ProtocolConfig {
private static final String METRICS_ENABLED = "metrics";
private static final String IGNORE_VERSION_CHECK = "ignore version check";
private static final String BACKGROUND_COMPILER_ENABLED = "background compiler";
private static final String DEBUG_MODE_ENABLED = "debug";
private static final String DETAILED_ERROR = "detailed error";
private static final String INJECTION_METHOD = "injection method";
private static final String CHAT_WARNINGS = "chat warnings";
private static final String SCRIPT_ENGINE_NAME = "script engine";
private static final String SUPPRESSED_REPORTS = "suppressed reports";
@@ -98,7 +92,7 @@ public class ProtocolConfig {
/**
* Load the last update time stamp from the file system.
*
*
* @return Last update time stamp.
*/
private long loadLastUpdate() {
@@ -106,7 +100,7 @@ public class ProtocolConfig {
if (dataFile.exists()) {
try {
return Long.parseLong(Files.toString(dataFile, Charsets.UTF_8));
return Long.parseLong(Files.toString(dataFile, StandardCharsets.UTF_8));
} catch (NumberFormatException e) {
plugin.getLogger().warning("Cannot parse " + dataFile + " as a number.");
} catch (IOException e) {
@@ -119,7 +113,7 @@ public class ProtocolConfig {
/**
* Store the given time stamp.
*
*
* @param value - time stamp to store.
*/
private void saveLastUpdate(long value) {
@@ -128,11 +122,12 @@ public class ProtocolConfig {
// The data folder must exist
dataFile.getParentFile().mkdirs();
if (dataFile.exists())
if (dataFile.exists()) {
dataFile.delete();
}
try {
Files.write(Long.toString(value), dataFile, Charsets.UTF_8);
Files.write(Long.toString(value), dataFile, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException("Cannot write " + dataFile, e);
}
@@ -140,7 +135,7 @@ public class ProtocolConfig {
/**
* Retrieve the file that is used to store the update time stamp.
*
*
* @return File storing the update time stamp.
*/
private File getLastUpdateFile() {
@@ -149,7 +144,7 @@ public class ProtocolConfig {
/**
* Load data sections.
*
*
* @param copyDefaults - whether or not to copy configuration defaults.
*/
private void loadSections(boolean copyDefaults) {
@@ -167,8 +162,9 @@ public class ProtocolConfig {
if (copyDefaults && (!getFile().exists() || global == null || updater == null)) {
loadingSections = true;
if (config != null)
if (config != null) {
config.options().copyDefaults(true);
}
plugin.saveDefaultConfig();
plugin.reloadConfig();
loadingSections = false;
@@ -180,7 +176,7 @@ public class ProtocolConfig {
/**
* Set a particular configuration key value pair.
*
*
* @param section - the configuration root.
* @param path - the path to the key.
* @param value - the value to set.
@@ -210,7 +206,7 @@ public class ProtocolConfig {
/**
* Retrieve a reference to the configuration file.
*
*
* @return Configuration file on disk.
*/
public File getFile() {
@@ -219,7 +215,7 @@ public class ProtocolConfig {
/**
* Determine if detailed error reporting is enabled. Default FALSE.
*
*
* @return TRUE if it is enabled, FALSE otherwise.
*/
public boolean isDetailedErrorReporting() {
@@ -227,57 +223,36 @@ public class ProtocolConfig {
}
/**
* Set whether or not detailed error reporting is enabled.
*
* @param value - TRUE if it is enabled, FALSE otherwise.
* Print warnings to players with protocol.info
* @return true if enabled, false if not
*/
public void setDetailedErrorReporting(boolean value) {
global.set(DETAILED_ERROR, value);
public boolean isChatWarnings() {
return getGlobalValue(CHAT_WARNINGS, true);
}
/**
* Retrieve whether or not ProtocolLib should determine if a new version has been released.
*
*
* @return TRUE if it should do this automatically, FALSE otherwise.
*/
public boolean isAutoNotify() {
return getUpdaterValue(UPDATER_NOTIFY, true);
}
/**
* Set whether or not ProtocolLib should determine if a new version has been released.
*
* @param value - TRUE to do this automatically, FALSE otherwise.
*/
public void setAutoNotify(boolean value) {
setConfig(updater, UPDATER_NOTIFY, value);
modCount++;
}
/**
* Retrieve whether or not ProtocolLib should automatically download the new version.
*
*
* @return TRUE if it should, FALSE otherwise.
*/
public boolean isAutoDownload() {
return updater != null && getUpdaterValue(UPDATER_DOWNLAD, false);
}
/**
* Set whether or not ProtocolLib should automatically download the new version.
*
* @param value - TRUE if it should. FALSE otherwise.
*/
public void setAutoDownload(boolean value) {
setConfig(updater, UPDATER_DOWNLAD, value);
modCount++;
}
/**
* Determine whether or not debug mode is enabled.
* <p>
* This grants access to the filter command.
*
*
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isDebug() {
@@ -286,7 +261,7 @@ public class ProtocolConfig {
/**
* Set whether or not debug mode is enabled.
*
*
* @param value - TRUE if it is enabled, FALSE otherwise.
*/
public void setDebug(boolean value) {
@@ -296,26 +271,16 @@ public class ProtocolConfig {
/**
* Retrieve an immutable list of every suppressed report type.
*
*
* @return Every suppressed report type.
*/
public ImmutableList<String> getSuppressedReports() {
return ImmutableList.copyOf(getGlobalValue(SUPPRESSED_REPORTS, new ArrayList<String>()));
}
/**
* Set the list of suppressed report types,
*
* @param reports - suppressed report types.
*/
public void setSuppressedReports(List<String> reports) {
global.set(SUPPRESSED_REPORTS, Lists.newArrayList(reports));
modCount++;
}
/**
* Retrieve the amount of time to wait until checking for a new update.
*
*
* @return The amount of time to wait.
*/
public long getAutoDelay() {
@@ -323,87 +288,27 @@ public class ProtocolConfig {
return Math.max(getUpdaterValue(UPDATER_DELAY, 0), DEFAULT_UPDATER_DELAY);
}
/**
* Set the amount of time to wait until checking for a new update.
* <p>
* This time must be greater than 59 seconds.
*
* @param delaySeconds - the amount of time to wait.
*/
public void setAutoDelay(long delaySeconds) {
// Silently fix the delay
if (delaySeconds < DEFAULT_UPDATER_DELAY)
delaySeconds = DEFAULT_UPDATER_DELAY;
setConfig(updater, UPDATER_DELAY, delaySeconds);
modCount++;
}
/**
* The version of Minecraft to ignore the built-in safety feature.
*
*
* @return The version to ignore ProtocolLib's satefy.
*/
public String getIgnoreVersionCheck() {
return getGlobalValue(IGNORE_VERSION_CHECK, "");
}
/**
* Sets under which version of Minecraft the version safety feature will be ignored.
* <p>
* This is useful if a server operator has tested and verified that a version of ProtocolLib works, but doesn't want or can't upgrade to a newer version.
*
* @param ignoreVersion - the version of Minecraft where the satefy will be disabled.
*/
public void setIgnoreVersionCheck(String ignoreVersion) {
setConfig(global, IGNORE_VERSION_CHECK, ignoreVersion);
modCount++;
}
/**
* Retrieve whether or not metrics is enabled.
*
*
* @return TRUE if metrics is enabled, FALSE otherwise.
*/
public boolean isMetricsEnabled() {
return getGlobalValue(METRICS_ENABLED, true);
}
/**
* Set whether or not metrics is enabled.
* <p>
* This setting will take effect next time ProtocolLib is started.
*
* @param enabled - whether or not metrics is enabled.
*/
public void setMetricsEnabled(boolean enabled) {
setConfig(global, METRICS_ENABLED, enabled);
modCount++;
}
/**
* Retrieve whether or not the background compiler for structure modifiers is enabled or not.
*
* @return TRUE if it is enabled, FALSE otherwise.
*/
public boolean isBackgroundCompilerEnabled() {
return getGlobalValue(BACKGROUND_COMPILER_ENABLED, true);
}
/**
* Set whether or not the background compiler for structure modifiers is enabled or not.
* <p>
* This setting will take effect next time ProtocolLib is started.
*
* @param enabled - TRUE if is enabled/running, FALSE otherwise.
*/
public void setBackgroundCompilerEnabled(boolean enabled) {
setConfig(global, BACKGROUND_COMPILER_ENABLED, enabled);
modCount++;
}
/**
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
*
*
* @return Last update time.
*/
public long getAutoLastTime() {
@@ -414,7 +319,7 @@ public class ProtocolConfig {
* Set the last time we updated, in seconds since 1970.01.01 00:00.
* <p>
* Note that this is not considered to modify the configuration, so the modification count will not be incremented.
*
*
* @param lastTimeSeconds - new last update time.
*/
public void setAutoLastTime(long lastTimeSeconds) {
@@ -424,7 +329,7 @@ public class ProtocolConfig {
/**
* Retrieve the unique name of the script engine to use for filtering.
*
*
* @return Script engine to use.
*/
public String getScriptEngineName() {
@@ -435,7 +340,7 @@ public class ProtocolConfig {
* Set the unique name of the script engine to use for filtering.
* <p>
* This setting will take effect next time ProtocolLib is started.
*
*
* @param name - name of the script engine to use.
*/
public void setScriptEngineName(String name) {
@@ -443,45 +348,9 @@ public class ProtocolConfig {
modCount++;
}
/**
* Retrieve the default injection method.
*
* @return Default method.
*/
public PlayerInjectHooks getDefaultMethod() {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
/**
* Retrieve the injection method that has been set in the configuration, or use a default value.
*
* @return Injection method to use.
* @throws IllegalArgumentException If the configuration option is malformed.
*/
public PlayerInjectHooks getInjectionMethod() throws IllegalArgumentException {
String text = global.getString(INJECTION_METHOD);
// Default hook if nothing has been set
PlayerInjectHooks hook = getDefaultMethod();
if (text != null)
hook = PlayerInjectHooks.valueOf(text.toUpperCase(Locale.ENGLISH).replace(" ", "_"));
return hook;
}
/**
* Set the starting injection method to use.
*
* @return Injection method.
*/
public void setInjectionMethod(PlayerInjectHooks hook) {
setConfig(global, INJECTION_METHOD, hook.name());
modCount++;
}
/**
* Retrieve the number of modifications made to this configuration.
*
*
* @return The number of modifications.
*/
public int getModificationCount() {
@@ -492,10 +361,12 @@ public class ProtocolConfig {
* Save the current configuration file.
*/
public void saveAll() {
if (valuesChanged)
if (valuesChanged) {
saveLastUpdate(lastUpdateTime);
if (configChanged)
}
if (configChanged) {
plugin.saveConfig();
}
// And we're done
valuesChanged = false;
@@ -1,39 +1,20 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2012 Kristian S.
* Stangeland
* <p>
* 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.
* <p>
* 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.
* <p>
* You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.comphenix.protocol;
import java.io.File;
import java.io.IOException;
import java.util.Set;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.executors.BukkitExecutors;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.DelegatedErrorReporter;
@@ -41,21 +22,34 @@ import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.DelayedSingleTask;
import com.comphenix.protocol.injector.InternalManager;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.EnhancerFactory;
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 java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
/**
* The main entry point for ProtocolLib.
@@ -63,119 +57,88 @@ import com.google.common.util.concurrent.ListeningScheduledExecutorService;
* @author Kristian
*/
public class ProtocolLib extends JavaPlugin {
// Every possible error or warning report type
public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration");
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType("Cannot delete old ProtocolLib configuration.");
public static final ReportType REPORT_CANNOT_PARSE_INJECTION_METHOD = new ReportType("Cannot parse injection method. Using default.");
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType(
"Cannot delete old ProtocolLib configuration.");
public static final ReportType REPORT_PLUGIN_LOAD_ERROR = new ReportType("Cannot load ProtocolLib.");
public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration");
public static final ReportType REPORT_PLUGIN_ENABLE_ERROR = new ReportType("Cannot enable ProtocolLib.");
public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType(
"Unable to enable metrics due to network problems.");
public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType(
"Unable to enable metrics due to network problems.");
public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType("Unable to retrieve current Minecraft version. Assuming %s");
public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType("Unable to detect conflicting plugin versions.");
public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType(
"Unable to retrieve current Minecraft version. Assuming %s");
public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType(
"Unable to detect conflicting plugin versions.");
public static final ReportType REPORT_CANNOT_REGISTER_COMMAND = new ReportType("Cannot register command %s: %s");
public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType("Unable to create packet timeout task.");
public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType(
"Unable to create packet timeout task.");
public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot perform automatic updates.");
// Update information
static final String BUKKIT_DEV_SLUG = "protocollib";
static final int BUKKIT_DEV_ID = 45564;
// Different commands
private enum ProtocolCommand {
FILTER,
PACKET,
PROTOCOL,
LOGGING;
}
/**
* The number of milliseconds per second.
*/
static final long MILLI_PER_SECOND = 1000;
static final long MILLI_PER_SECOND = TimeUnit.SECONDS.toMillis(1);
private static final int ASYNC_MANAGER_DELAY = 1;
private static final String PERMISSION_INFO = "protocol.info";
// There should only be one protocol manager, so we'll make it static
private static InternalManager protocolManager;
// Error reporter
private static ErrorReporter reporter = new BasicErrorReporter();
// Strongly typed configuration
// these fields are only existing once, we can make them static
private static Logger logger;
private static ProtocolConfig config;
// Metrics and statistics
private static InternalManager protocolManager;
private static ErrorReporter reporter = new BasicErrorReporter();
private Statistics statistics;
// Executors
private static ListeningScheduledExecutorService executorAsync;
private static ListeningScheduledExecutorService executorSync;
// Structure compiler
private BackgroundCompiler backgroundCompiler;
// Used to clean up server packets that have expired, but mostly required to simulate
// recieving client packets.
private int packetTask = -1;
private int tickCounter = 0;
private static final int ASYNC_MANAGER_DELAY = 1;
// Used to unhook players after a delay
private DelayedSingleTask unhookTask;
// Settings/options
private int configExpectedMod = -1;
// Updater
// updater
private Updater updater;
public static boolean UPDATES_DISABLED;
// Logger
private static Logger logger;
private Handler redirectHandler;
// Commands
// commands
private CommandProtocol commandProtocol;
private CommandPacket commandPacket;
private CommandFilter commandFilter;
private PacketLogging packetLogging;
// Whether or not disable is not needed
// Whether disabling field resetting is needed
private boolean skipDisable;
@Override
public void onLoad() {
// Logging
logger = getLogger();
logger = this.getLogger();
ProtocolLogger.init(this);
// Initialize enhancer factory
EnhancerFactory.getInstance().setClassLoader(getClassLoader());
// Initialize executors
executorAsync = BukkitExecutors.newAsynchronous(this);
executorSync = BukkitExecutors.newSynchronous(this);
ByteBuddyFactory.getInstance().setClassLoader(this.getClassLoader());
// Add global parameters
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
reporter = getFilteredReporter(detailedReporter);
reporter = this.getFilteredReporter(detailedReporter);
// Configuration
saveDefaultConfig();
reloadConfig();
this.saveDefaultConfig();
this.reloadConfig();
try {
config = new ProtocolConfig(this);
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
} catch (Exception exception) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(exception));
// Load it again
if (deleteConfig()) {
if (this.deleteConfig()) {
config = new ProtocolConfig(this);
} else {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DELETE_CONFIG));
@@ -186,6 +149,7 @@ public class ProtocolLib extends JavaPlugin {
if (config.isDebug()) {
logger.warning("Debug mode is enabled!");
}
// And the state of the error reporter
if (config.isDetailedErrorReporting()) {
detailedReporter.setDetailedReporting(true);
@@ -194,52 +158,33 @@ public class ProtocolLib extends JavaPlugin {
try {
// Check for other versions
checkConflictingVersions();
this.checkConflictingVersions();
// Handle unexpected Minecraft versions
MinecraftVersion version = verifyMinecraftVersion();
MinecraftVersion version = this.verifyMinecraftVersion();
// Set updater - this will not perform any update automatically
updater = Updater.create(this, BUKKIT_DEV_ID, getFile(), UpdateType.NO_DOWNLOAD, true);
this.updater = Updater.create(this, 0, this.getFile(), UpdateType.NO_DOWNLOAD, true);
unhookTask = new DelayedSingleTask(this);
// api init
protocolManager = PacketFilterManager.newBuilder()
.classLoader(getClassLoader())
.server(getServer())
.server(this.getServer())
.library(this)
.minecraftVersion(version)
.unhookTask(unhookTask)
.reporter(reporter)
.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);
// Update injection hook
try {
PlayerInjectHooks hook = config.getInjectionMethod();
// Only update the hook if it's different
if (!protocolManager.getPlayerHook().equals(hook)) {
logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook);
protocolManager.setPlayerHook(hook);
}
} catch (IllegalArgumentException e) {
reporter.reportWarning(config, Report.newBuilder(REPORT_CANNOT_PARSE_INJECTION_METHOD).error(e));
}
// Send logging information to player listeners too
initializeCommands();
setupBroadcastUsers(PERMISSION_INFO);
this.initializeCommands();
this.setupBroadcastUsers(PERMISSION_INFO);
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
} catch (Exception e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
disablePlugin();
this.disablePlugin();
}
}
@@ -251,18 +196,18 @@ public class ProtocolLib extends JavaPlugin {
for (ProtocolCommand command : ProtocolCommand.values()) {
try {
switch (command) {
case PROTOCOL:
commandProtocol = new CommandProtocol(reporter, this, updater, config);
break;
case FILTER:
commandFilter = new CommandFilter(reporter, this, config);
break;
case PACKET:
commandPacket = new CommandPacket(reporter, this, logger, commandFilter, protocolManager);
break;
case LOGGING:
packetLogging = new PacketLogging(this, protocolManager);
break;
case PROTOCOL:
this.commandProtocol = new CommandProtocol(reporter, this, this.updater, config);
break;
case FILTER:
this.commandFilter = new CommandFilter(reporter, this, config);
break;
case PACKET:
this.commandPacket = new CommandPacket(reporter, this, logger, this.commandFilter, protocolManager);
break;
case LOGGING:
this.packetLogging = new PacketLogging(this, protocolManager);
break;
}
} catch (OutOfMemoryError e) {
throw e;
@@ -277,12 +222,13 @@ public class ProtocolLib extends JavaPlugin {
/**
* Retrieve a error reporter that may be filtered by the configuration.
*
* @return The new default error reporter.
*/
private ErrorReporter getFilteredReporter(ErrorReporter reporter) {
return new DelegatedErrorReporter(reporter) {
private int lastModCount = -1;
private Set<String> reports = Sets.newHashSet();
private Set<String> reports = new HashSet<>();
@Override
protected Report filterReport(Object sender, Report report, boolean detailed) {
@@ -290,19 +236,20 @@ public class ProtocolLib extends JavaPlugin {
String canonicalName = ReportType.getReportName(sender, report.getType());
String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase();
if (config != null && config.getModificationCount() != lastModCount) {
if (config != null && config.getModificationCount() != this.lastModCount) {
// Update our cached set again
reports = Sets.newHashSet(config.getSuppressedReports());
lastModCount = config.getModificationCount();
this.reports = new HashSet<>(config.getSuppressedReports());
this.lastModCount = config.getModificationCount();
}
// Cancel reports either on the full canonical name, or just the report name
if (reports.contains(canonicalName) || reports.contains(reportName))
if (this.reports.contains(canonicalName) || this.reports.contains(reportName)) {
return null;
}
} catch (Exception e) {
// Only report this with a minor message
logger.warning("Error filtering reports: " + e.toString());
logger.warning("Error filtering reports: " + e);
}
// Don't filter anything
return report;
@@ -326,16 +273,17 @@ public class ProtocolLib extends JavaPlugin {
private void setupBroadcastUsers(final String permission) {
// Guard against multiple calls
if (redirectHandler != null)
if (this.redirectHandler != null) {
return;
}
// Broadcast information to every user too
redirectHandler = new Handler() {
this.redirectHandler = new Handler() {
@Override
public void publish(LogRecord record) {
// Only display warnings and above
if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
commandPacket.broadcastMessageSilently(record.getMessage(), permission);
ProtocolLib.this.commandPacket.broadcastMessageSilently(record.getMessage(), permission);
}
}
@@ -350,23 +298,19 @@ public class ProtocolLib extends JavaPlugin {
}
};
logger.addHandler(redirectHandler);
logger.addHandler(this.redirectHandler);
}
@Override
public void onEnable() {
try {
Server server = getServer();
Server server = this.getServer();
PluginManager manager = server.getPluginManager();
// Don't do anything else!
if (manager == null)
return;
// Silly plugin reloaders!
if (protocolManager == null) {
Logger directLogging = Logger.getLogger("Minecraft");
String[] message = new String[] {
String[] message = new String[]{
" ProtocolLib does not support plugin reloaders! ", " Please use the built-in reload command! "
};
@@ -375,59 +319,48 @@ public class ProtocolLib extends JavaPlugin {
directLogging.severe(line);
}
disablePlugin();
this.disablePlugin();
return;
}
// Check for incompatible plugins
checkForIncompatibility(manager);
// Initialize background compiler
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);
BackgroundCompiler.setInstance(backgroundCompiler);
logger.info("Started structure compiler thread.");
} else {
logger.info("Structure compiler thread has been disabled.");
}
this.checkForIncompatibility(manager);
// Set up command handlers
registerCommand(CommandProtocol.NAME, commandProtocol);
registerCommand(CommandPacket.NAME, commandPacket);
registerCommand(CommandFilter.NAME, commandFilter);
registerCommand(PacketLogging.NAME, packetLogging);
this.registerCommand(CommandProtocol.NAME, this.commandProtocol);
this.registerCommand(CommandPacket.NAME, this.commandPacket);
this.registerCommand(CommandFilter.NAME, this.commandFilter);
this.registerCommand(PacketLogging.NAME, this.packetLogging);
// Player login and logout events
protocolManager.registerEvents(manager, this);
// Worker that ensures that async packets are eventually sent
// It also performs the update check.
createPacketTask(server);
this.createPacketTask(server);
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e));
disablePlugin();
this.disablePlugin();
return;
}
// Try to enable statistics
try {
if (config.isMetricsEnabled()) {
statistics = new Statistics(this);
this.statistics = new Statistics(this);
}
} catch (OutOfMemoryError e) {
throw e;
} catch (IOException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(statistics));
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(this.statistics));
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam(statistics));
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam(
this.statistics));
}
}
// Plugin authors: Notify me to remove these
private void checkForIncompatibility(PluginManager manager) {
for (String plugin : ProtocolLibrary.INCOMPATIBLE) {
if (manager.getPlugin(plugin) != null) {
@@ -444,26 +377,31 @@ public class ProtocolLib extends JavaPlugin {
}
}
// Plugin authors: Notify me to remove these
// Used to check Minecraft version
private MinecraftVersion verifyMinecraftVersion() {
MinecraftVersion minimum = new MinecraftVersion(ProtocolLibrary.MINIMUM_MINECRAFT_VERSION);
MinecraftVersion maximum = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION);
try {
MinecraftVersion current = new MinecraftVersion(getServer());
MinecraftVersion current = new MinecraftVersion(this.getServer());
// Skip certain versions
if (!config.getIgnoreVersionCheck().equals(current.getVersion())) {
// We'll just warn the user for now
if (current.compareTo(minimum) < 0)
if (current.compareTo(minimum) < 0) {
logger.warning("Version " + current + " is lower than the minimum " + minimum);
if (current.compareTo(maximum) > 0)
}
if (current.compareTo(maximum) > 0) {
logger.warning("Version " + current + " has not yet been tested! Proceed with caution.");
}
}
return current;
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e).messageParam(maximum));
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e).messageParam(maximum));
// Unknown version - just assume it is the latest
return maximum;
@@ -472,16 +410,16 @@ public class ProtocolLib extends JavaPlugin {
private void checkConflictingVersions() {
Pattern ourPlugin = Pattern.compile("ProtocolLib-(.*)\\.jar");
MinecraftVersion currentVersion = new MinecraftVersion(getDescription().getVersion());
MinecraftVersion currentVersion = new MinecraftVersion(this.getDescription().getVersion());
MinecraftVersion newestVersion = null;
// Skip the file that contains this current instance however
File loadedFile = getFile();
File loadedFile = this.getFile();
try {
// Scan the plugin folder for newer versions of ProtocolLib
// The plugin folder isn't always plugins/
File pluginFolder = getDataFolder().getParentFile();
File pluginFolder = this.getDataFolder().getParentFile();
File[] candidates = pluginFolder.listFiles();
if (candidates != null) {
@@ -509,7 +447,7 @@ public class ProtocolLib extends JavaPlugin {
// See if the newest version is actually higher
if (newestVersion != null && currentVersion.compareTo(newestVersion) < 0) {
// We don't need to set internal classes or instances to NULL - that would break the other loaded plugin
skipDisable = true;
this.skipDisable = true;
throw new IllegalStateException(String.format(
"Detected a newer version of ProtocolLib (%s) in plugin folder than the current (%s). Disabling.",
@@ -520,10 +458,11 @@ public class ProtocolLib extends JavaPlugin {
private void registerCommand(String name, CommandExecutor executor) {
try {
// Ignore these - they must have printed an error already
if (executor == null)
if (executor == null) {
return;
}
PluginCommand command = getCommand(name);
PluginCommand command = this.getCommand(name);
// Try to load the command
if (command != null) {
@@ -532,7 +471,8 @@ public class ProtocolLib extends JavaPlugin {
throw new RuntimeException("plugin.yml might be corrupt.");
}
} catch (RuntimeException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
}
}
@@ -540,44 +480,42 @@ public class ProtocolLib extends JavaPlugin {
* Disable the current plugin.
*/
private void disablePlugin() {
getServer().getPluginManager().disablePlugin(this);
this.getServer().getPluginManager().disablePlugin(this);
}
private void createPacketTask(Server server) {
try {
if (packetTask >= 0)
if (this.packetTask >= 0) {
throw new IllegalStateException("Packet task has already been created");
}
// Attempt to create task
packetTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
@Override
public void run() {
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
this.packetTask = server.getScheduler().scheduleSyncRepeatingTask(this, () -> {
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
// We KNOW we're on the main thread at the moment
manager.sendProcessedPackets(tickCounter++, true);
// We KNOW we're on the main thread at the moment
manager.sendProcessedPackets(ProtocolLib.this.tickCounter++, true);
// House keeping
updateConfiguration();
// House keeping
ProtocolLib.this.updateConfiguration();
// Check for updates too
if (!UPDATES_DISABLED && (tickCounter % 20) == 0) {
checkUpdates();
}
// Check for updates too
if (!ProtocolLibrary.updatesDisabled() && (ProtocolLib.this.tickCounter % 20) == 0) {
ProtocolLib.this.checkUpdates();
}
}, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY);
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
if (packetTask == -1) {
if (this.packetTask == -1) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CREATE_TIMEOUT_TASK).error(e));
}
}
}
private void updateConfiguration() {
if (config != null && config.getModificationCount() != configExpectedMod) {
configExpectedMod = config.getModificationCount();
if (config != null && config.getModificationCount() != this.configExpectedMod) {
this.configExpectedMod = config.getModificationCount();
// Update the debug flag
protocolManager.setDebug(config.isDebug());
@@ -587,58 +525,64 @@ public class ProtocolLib extends JavaPlugin {
private void checkUpdates() {
// Ignore milliseconds - it's pointless
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
try {
long updateTime = config.getAutoLastTime() + config.getAutoDelay();
// Should we update?
if (currentTime > updateTime && !updater.isChecking()) {
if (currentTime > updateTime && !this.updater.isChecking()) {
// Initiate the update as if it came from the console
if (config.isAutoDownload())
commandProtocol.updateVersion(getServer().getConsoleSender(), false);
else if (config.isAutoNotify())
commandProtocol.checkVersion(getServer().getConsoleSender(), false);
else
commandProtocol.updateFinished();
if (config.isAutoDownload()) {
this.commandProtocol.updateVersion(this.getServer().getConsoleSender(), false);
} else if (config.isAutoNotify()) {
this.commandProtocol.checkVersion(this.getServer().getConsoleSender(), false);
} else {
this.commandProtocol.updateFinished();
}
}
} catch (Exception e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
UPDATES_DISABLED = true;
ProtocolLibrary.disableUpdates();
}
}
@Override
public void onDisable() {
if (skipDisable) {
if (this.skipDisable) {
return;
}
// Disable compiler
if (backgroundCompiler != null) {
backgroundCompiler.shutdownAll();
backgroundCompiler = null;
BackgroundCompiler.setInstance(null);
// that reloading the server might break ProtocolLib / plugins depending on it
if (Util.isCurrentlyReloading()) {
logger.severe("╔══════════════════════════════════════════════════════════════════╗");
logger.severe("║ WARNING ║");
logger.severe("║ RELOADING THE SERVER WHILE PROTOCOL LIB IS ENABLED MIGHT ║");
logger.severe("║ LEAD TO UNEXPECTED ERRORS! ║");
logger.severe("║ ║");
logger.severe("║ Consider to cleanly restart your server if you encounter ║");
logger.severe("║ any issues related to Protocol Lib before opening an issue ║");
logger.severe("║ on GitHub! ║");
logger.severe("╚══════════════════════════════════════════════════════════════════╝");
}
// Clean up
if (packetTask >= 0) {
getServer().getScheduler().cancelTask(packetTask);
packetTask = -1;
if (this.packetTask >= 0) {
this.getServer().getScheduler().cancelTask(this.packetTask);
this.packetTask = -1;
}
// And redirect handler too
if (redirectHandler != null) {
logger.removeHandler(redirectHandler);
if (this.redirectHandler != null) {
logger.removeHandler(this.redirectHandler);
}
if (protocolManager != null)
if (protocolManager != null) {
protocolManager.close();
else
} else {
return; // Plugin reloaders!
}
if (unhookTask != null)
unhookTask.close();
protocolManager = null;
statistics = null;
this.statistics = null;
// To clean up global parameters
reporter = new BasicErrorReporter();
@@ -647,15 +591,24 @@ public class ProtocolLib extends JavaPlugin {
/**
* Retrieve the metrics instance used to measure users of this library.
* <p>
* Note that this method may return NULL when the server is reloading or shutting down. It is also
* NULL if metrics has been disabled.
* Note that this method may return NULL when the server is reloading or shutting down. It is also NULL if metrics has
* been disabled.
*
* @return Metrics instance container.
*/
public Statistics getStatistics() {
return statistics;
return this.statistics;
}
public ProtocolConfig getProtocolConfig() {
return config;
}
// Different commands
private enum ProtocolCommand {
FILTER,
PACKET,
PROTOCOL,
LOGGING
}
}
@@ -1,36 +1,33 @@
/**
* 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
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2016 dmulloy2
* <p>
* 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.
* <p>
* 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.
* <p>
* You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.comphenix.protocol;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang.Validate;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.comphenix.protocol.utility.MinecraftVersion;
import java.util.List;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang.Validate;
import org.bukkit.plugin.Plugin;
/**
* The main entry point for ProtocolLib.
* @author dmulloy2
*/
public class ProtocolLibrary {
/**
* The minimum version ProtocolLib has been tested with.
*/
@@ -39,38 +36,32 @@ public class ProtocolLibrary {
/**
* The maximum version ProtocolLib has been tested with.
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "1.15.1";
public static final String MAXIMUM_MINECRAFT_VERSION = MinecraftVersion.LATEST.getVersion();
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.14.4) was released.
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.19.2) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2019-12-17";
public static final String MINECRAFT_LAST_RELEASE_DATE = "2022-08-05";
/**
* Plugins that are currently incompatible with ProtocolLib.
*/
public static final List<String> INCOMPATIBLE = Arrays.asList("TagAPI");
public static final List<String> INCOMPATIBLE = ImmutableList.of("TagAPI");
private static Plugin plugin;
private static ProtocolConfig config;
private static ProtocolManager manager;
private static ErrorReporter reporter = new BasicErrorReporter();
private static ListeningScheduledExecutorService executorAsync;
private static ListeningScheduledExecutorService executorSync;
private static boolean updatesDisabled;
private static boolean initialized;
protected static void init(Plugin plugin, ProtocolConfig config, ProtocolManager manager, ErrorReporter reporter,
ListeningScheduledExecutorService executorAsync, ListeningScheduledExecutorService executorSync) {
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 +97,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.
*/
@@ -134,7 +105,7 @@ public class ProtocolLibrary {
}
/**
* Whether or not updates are currently disabled.
* Whether updates are currently disabled.
* @return True if it is, false if not
*/
public static boolean updatesDisabled() {
@@ -20,7 +20,7 @@ import java.text.MessageFormat;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.utility.MinecraftVersion;
/**
* @author dmulloy2
@@ -29,7 +29,12 @@ public class ProtocolLogger {
private static boolean debugEnabled = false;
private static Logger logger = Logger.getLogger("Minecraft");
protected static void init(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();
try {
@@ -77,4 +82,10 @@ public class ProtocolLogger {
logger.log(Level.WARNING, "[Debug] " + message, ex);
}
}
public static void warnAbove(MinecraftVersion version, String message, Object... args) {
if (version.atOrAbove()) {
log(Level.WARNING, message, args);
}
}
}
@@ -17,16 +17,6 @@
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Set;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist;
@@ -36,250 +26,218 @@ import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
/**
* Represents an API for accessing the Minecraft protocol.
*
* @author Kristian
*/
public interface ProtocolManager extends PacketStream {
/**
* Retrieve the protocol version of a given player.
* <p>
* This only really makes sense of a server that support clients of multiple Minecraft versions, such as Spigot #1628.
* This only really makes sense of a server that support clients of multiple Minecraft versions, such as Spigot
* #1628.
*
* @param player - the player.
* @return The associated protocol version, or {@link Integer#MIN_VALUE} if unknown.
*/
public int getProtocolVersion(Player player);
int getProtocolVersion(Player player);
/**
* Send a packet to the given player.
* <p>
* Re-sending a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()}
* to delay a packet until a certain condition has been met.
*
* Re-sending a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} to
* delay a packet until a certain condition has been met.
*
* @param receiver - the receiver.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occurred when sending the packet.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
*/
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
throws InvocationTargetException;
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters);
/**
* Simulate receiving a certain packet from a given player.
* <p>
* Receiving a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()}
* to delay a packet until a certain condition has been met.
*
* @param sender - the sender.
* @param packet - the packet that was sent.
* Receiving a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} to delay
* a packet until a certain condition has been met.
*
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
throws IllegalAccessException, InvocationTargetException;
void receiveClientPacket(Player sender, PacketContainer packet, boolean filters);
/**
* Broadcast a given packet to every connected player on the server.
*
* @param packet - the packet to broadcast.
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
*/
public void broadcastServerPacket(PacketContainer packet);
void broadcastServerPacket(PacketContainer packet);
/**
* Broadcast a packet to every player that is receiving information about a given entity.
* <p>
* This is usually every player in the same world within an observable distance. If the entity is a
* player, it will only be included if <i>includeTracker</i> is TRUE.
* @param packet - the packet to broadcast.
* @param entity - the entity whose trackers we will inform.
* This is usually every player in the same world within an observable distance. If the entity is a player, it will
* only be included if <i>includeTracker</i> is TRUE.
*
* @param packet - the packet to broadcast.
* @param entity - the entity whose trackers we will inform.
* @param includeTracker - whether or not to also transmit the packet to the entity, if it is a tracker.
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
*/
public void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker);
void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker);
/**
* Broadcast a packet to every player within the given maximum observer distance.
* @param packet - the packet to broadcast.
* @param origin - the origin to consider when calculating the distance to each observer.
*
* @param packet - the packet to broadcast.
* @param origin - the origin to consider when calculating the distance to each observer.
* @param maxObserverDistance - the maximum distance to the origin.
*/
public void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance);
void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance);
void broadcastServerPacket(PacketContainer packet, Collection<? extends Player> targetPlayers);
/**
* Retrieves a list of every registered packet listener.
*
* @return Every registered packet listener.
*/
public ImmutableSet<PacketListener> getPacketListeners();
ImmutableSet<PacketListener> getPacketListeners();
/**
* Adds a packet listener.
* <p>
* Adding an already registered listener has no effect. If you need to change the packets
* the current listener is observing, you must first remove the packet listener before you
* can register it again.
* Adding an already registered listener has no effect. If you need to change the packets the current listener is
* observing, you must first remove the packet listener before you can register it again.
*
* @param listener - new packet listener.
*/
public void addPacketListener(PacketListener listener);
void addPacketListener(PacketListener listener);
/**
* Removes a given packet listener.
* <p>
* Attempting to remove a listener that doesn't exist has no effect.
*
* @param listener - the packet listener to remove.
*/
public void removePacketListener(PacketListener listener);
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.
*
* @param type - packet type.
* @return New encapsulated Minecraft packet.
*/
PacketContainer createPacket(PacketType type);
/**
* 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.
* If set to true, the <i>forceDefaults</i> option will force the system to automatically give non-primitive fields in
* the packet sensible default values. For instance, certain packets - like Packet60Explosion - require a List or Set
* to be non-null. If the forceDefaults option is true, the List or Set will be automatically created.
*
* @param type - packet type.
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
* @return New encapsulated Minecraft packet.
*/
@Deprecated
public PacketContainer createPacket(int id, boolean forceDefaults);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* <p>
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
* give non-primitive fields in the packet sensible default values. For instance, certain
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
* forceDefaults option is true, the List or Set will be automatically created.
*
* @param type - packet type.
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(PacketType type, boolean forceDefaults);
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 type - the packet type.
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
@Deprecated
public PacketConstructor createPacketConstructor(int id, Object... arguments);
/**
* Construct a packet using the special builtin Minecraft constructors.
* @param type - the packet type.
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
/**
* Completely resend an entity to a list of clients.
* <p>
* Note that this method is NOT thread safe. If you call this method from anything
* but the main thread, it will throw an exception.
* @param entity - entity to refresh.
* Note that this method is NOT thread safe. If you call this method from anything but the main thread, it will throw
* an exception.
*
* @param entity - entity to refresh.
* @param observers - the clients to update.
*/
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
void updateEntity(Entity entity, List<Player> observers);
/**
* Retrieve the associated entity.
*
* @param container - the world the entity belongs to.
* @param id - the unique ID of the entity.
* @param id - the unique ID of the entity.
* @return The associated entity.
* @throws FieldAccessException Reflection failed.
*/
public Entity getEntityFromID(World container, int id) throws FieldAccessException;
Entity getEntityFromID(World container, int id);
/**
* Retrieve every client that is receiving information about a given entity.
*
* @param entity - the entity that is being tracked.
* @return Every client/player that is tracking the given entity.
* @throws FieldAccessException If reflection failed.
*/
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException;
/**
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
* <p>
* Deprecated: Use {@link #getSendingFilterTypes()} instead.
* @return Every filtered server packet.
*/
@Deprecated
public Set<Integer> getSendingFilters();
List<Player> getEntityTrackers(Entity entity);
/**
* 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);
}
@@ -17,22 +17,24 @@
package com.comphenix.protocol;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Range;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
/**
* Used to parse ranges in CommandPacket.
*
* @author Kristian
*/
class RangeParser {
final class RangeParser {
private RangeParser() {}
/**
* Parse a range from a given text.
* @param text - the text.
@@ -40,20 +42,18 @@ class RangeParser {
* @return The parsed ranges.
*/
public static List<Range<Integer>> getRanges(String text, Range<Integer> legalRange) {
return getRanges(new ArrayDeque<String>(Arrays.asList(text)), legalRange);
return getRanges(new ArrayDeque<>(Collections.singletonList(text)), legalRange);
}
/**
* Parse ranges from an array of elements.
* @param args - array of elements.
* @param offset - beginning offset.
* @param lastIndex - the last index of the array to read.
* @param input the input to parse the ranges from
* @param legalRange - range of legal values.
* @return The parsed ranges.
*/
public static List<Range<Integer>> getRanges(Deque<String> input, Range<Integer> legalRange) {
List<String> tokens = tokenizeInput(input);
List<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
List<Range<Integer>> ranges = new ArrayList<>();
for (int i = 0; i < tokens.size(); i++) {
Range<Integer> range;
@@ -96,7 +96,7 @@ class RangeParser {
* @return A simplified list of ranges.
*/
private static List<Range<Integer>> simplify(List<Range<Integer>> ranges, int maximum) {
List<Range<Integer>> result = new ArrayList<Range<Integer>>();
List<Range<Integer>> result = new ArrayList<>();
boolean[] set = new boolean[maximum + 1];
int start = -1;
@@ -125,7 +125,7 @@ class RangeParser {
}
private static List<String> tokenizeInput(Deque<String> input) {
List<String> tokens = new ArrayList<String>();
List<String> tokens = new ArrayList<>();
// Tokenize the input
while (!input.isEmpty()) {
@@ -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,11 +33,13 @@ 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.
@@ -88,7 +86,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 = ConcurrentHashMap.newKeySet();
this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners);
this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
@@ -327,22 +325,12 @@ public class AsyncFilterManager implements AsynchronousManager {
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();
@@ -121,16 +121,10 @@ public class AsyncListenerHandler {
}
private void startWarningTask() {
warningTask = filterManager.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable() {
@Override
public void run() {
ProtocolLibrary.getErrorReporter().reportWarning(AsyncListenerHandler.this, Report.
newBuilder(REPORT_HANDLER_NOT_STARTED).
messageParam(listener.getPlugin(), AsyncListenerHandler.this).
build()
);
}
}, 2 * TICKS_PER_SECOND);
warningTask = filterManager.getScheduler().scheduleSyncDelayedTask(getPlugin(), () -> ProtocolLibrary.getErrorReporter().reportWarning(AsyncListenerHandler.this, Report.
newBuilder(REPORT_HANDLER_NOT_STARTED).
messageParam(listener.getPlugin(), AsyncListenerHandler.this).
build()), 2 * TICKS_PER_SECOND);
}
private void stopWarningTask() {
@@ -287,19 +281,16 @@ public class AsyncListenerHandler {
final AsyncRunnable listenerLoop = getListenerLoop();
stopWarningTask();
scheduleAsync(new Runnable() {
@Override
public void run() {
Thread thread = Thread.currentThread();
String previousName = thread.getName();
String workerName = getFriendlyWorkerName(listenerLoop.getID());
scheduleAsync(() -> {
Thread thread = Thread.currentThread();
// Add the friendly worker name
thread.setName(workerName);
listenerLoop.run();
thread.setName(previousName);
}
String previousName = thread.getName();
String workerName = getFriendlyWorkerName(listenerLoop.getID());
// Add the friendly worker name
thread.setName(workerName);
listenerLoop.run();
thread.setName(previousName);
});
}
@@ -335,12 +326,7 @@ public class AsyncListenerHandler {
final AsyncRunnable listenerLoop = getListenerLoop();
final Function<AsyncRunnable, Void> delegateCopy = executor;
scheduleAsync(new Runnable() {
@Override
public void run() {
delegateCopy.apply(listenerLoop);
}
});
scheduleAsync(() -> delegateCopy.apply(listenerLoop));
}
private void scheduleAsync(Runnable runnable) {
@@ -423,31 +409,28 @@ public class AsyncListenerHandler {
if (syncTask < 0) {
stopWarningTask();
syncTask = filterManager.getScheduler().scheduleSyncRepeatingTask(getPlugin(), new Runnable() {
@Override
public void run() {
long stopTime = System.nanoTime() + unit.convert(time, TimeUnit.NANOSECONDS);
while (!cancelled) {
PacketEvent packet = queuedPackets.poll();
if (packet == INTERUPT_PACKET || packet == WAKEUP_PACKET) {
// Sorry, asynchronous threads!
queuedPackets.add(packet);
// Try again next tick
break;
} else if (packet != null && packet.getAsyncMarker() != null) {
processPacket(workerID, packet, "onSyncPacket()");
} else {
// No more packets left - wait a tick
break;
}
// Check time here, ensuring that we at least process one packet
if (System.nanoTime() < stopTime)
break;
syncTask = filterManager.getScheduler().scheduleSyncRepeatingTask(getPlugin(), () -> {
long stopTime = System.nanoTime() + unit.convert(time, TimeUnit.NANOSECONDS);
while (!cancelled) {
PacketEvent packet = queuedPackets.poll();
if (packet == INTERUPT_PACKET || packet == WAKEUP_PACKET) {
// Sorry, asynchronous threads!
queuedPackets.add(packet);
// Try again next tick
break;
} else if (packet != null && packet.getAsyncMarker() != null) {
processPacket(workerID, packet, "onSyncPacket()");
} else {
// No more packets left - wait a tick
break;
}
// Check time here, ensuring that we at least process one packet
if (System.nanoTime() < stopTime)
break;
}
}, tickDelay, tickDelay);
@@ -42,11 +42,11 @@ import com.google.common.primitives.Longs;
* Contains information about the packet that is being processed by asynchronous listeners.
* <p>
* Asynchronous listeners can use this to set packet timeout or transmission order.
*
*
* @author Kristian
*/
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* Generated by Eclipse.
*/
@@ -56,52 +56,52 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* Default number of milliseconds until a packet will rejected.
*/
public static final int DEFAULT_TIMEOUT_DELTA = 1800 * 1000;
/**
* Default number of packets to skip.
*/
public static final int DEFAULT_SENDING_DELTA = 0;
/**
* The packet stream responsible for transmitting the packet when it's done processing.
*/
private transient PacketStream packetStream;
/**
* Current list of async packet listeners.
*/
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
// Timeout handling
private long initialTime;
private long timeout;
// Packet order
private long originalSendingIndex;
private long newSendingIndex;
// Used to determine if a packet must be reordered in the sending queue
private Long queuedSendingIndex;
// Whether or not the packet has been processed by the listeners
private volatile boolean processed;
// Whether or not the packet has been sent
private volatile boolean transmitted;
// Whether or not the asynchronous processing itself should be cancelled
private volatile boolean asyncCancelled;
// Whether or not to delay processing
private AtomicInteger processingDelay = new AtomicInteger();
// Used to synchronize processing on the shared PacketEvent
private Object processingLock = new Object();
// Used to identify the asynchronous worker
private transient AsyncListenerHandler listenerHandler;
private transient int workerID;
// Determine if Minecraft processes this packet asynchronously
private volatile static Method isMinecraftAsync;
private volatile static boolean alwaysSync;
@@ -113,18 +113,18 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) {
if (packetStream == null)
throw new IllegalArgumentException("packetStream cannot be NULL");
this.packetStream = packetStream;
// Timeout
this.initialTime = initialTime;
this.timeout = initialTime + timeoutDelta;
// Sending index
this.originalSendingIndex = sendingIndex;
this.newSendingIndex = sendingIndex;
}
/**
* Retrieve the time the packet was initially queued for asynchronous processing.
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
@@ -140,7 +140,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
public long getTimeout() {
return timeout;
}
/**
* Set the time the packet will be forcefully rejected.
* @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00.
@@ -219,13 +219,13 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* <p>
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
* using the {@link #getProcessingLock()} method.
*
*
* @return The new processing delay.
*/
public int incrementProcessingDelay() {
return processingDelay.incrementAndGet();
}
/**
* Decrement the number of times this packet must be signalled as done before it's transmitted.
* @return The new processing delay. If zero, the packet should be sent.
@@ -233,7 +233,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
int decrementProcessingDelay() {
return processingDelay.decrementAndGet();
}
/**
* Retrieve the number of times a packet must be signalled to be done before it's sent.
* @return Number of processing delays.
@@ -241,7 +241,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
public int getProcessingDelay() {
return processingDelay.get();
}
/**
* Whether or not this packet is or has been queued for processing.
* @return TRUE if it has, FALSE otherwise.
@@ -296,7 +296,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
public boolean hasExpired() {
return hasExpired(System.currentTimeMillis());
}
/**
* Determine if this packet has expired given this time.
* @param currentTime - the current time in milliseconds since 01.01.1970 00:00.
@@ -305,7 +305,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
public boolean hasExpired(long currentTime) {
return timeout < currentTime;
}
/**
* Determine if the asynchronous handling should be cancelled.
* @return TRUE if it should, FALSE otherwise.
@@ -319,7 +319,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* <p>
* This is only relevant during the synchronous processing. Asynchronous
* listeners should use the normal cancel-field to cancel a PacketEvent.
*
*
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
*/
public void setAsyncCancelled(boolean asyncCancelled) {
@@ -369,7 +369,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
return listenerTraversal;
}
/**
* Set the iterator for the next listener.
* @param listenerTraversal - the new async packet listener iterator.
@@ -377,28 +377,22 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
this.listenerTraversal = listenerTraversal;
}
/**
* Transmit a given packet to the current packet stream.
* @param event - the packet to send.
* @throws IOException If the packet couldn't be sent.
*/
void sendPacket(PacketEvent event) throws IOException {
try {
if (event.isServerPacket()) {
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
} else {
packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
}
transmitted = true;
} catch (InvocationTargetException e) {
throw new IOException("Cannot send packet", e);
} catch (IllegalAccessException e) {
throw new IOException("Cannot send packet", e);
if (event.isServerPacket()) {
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
} else {
packetStream.receiveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event),
false);
}
transmitted = true;
}
/**
* Determine if Minecraft allows asynchronous processing of this packet.
* @param event - packet event
@@ -413,7 +407,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
// This will occur in 1.2.5 (or possibly in later versions)
List<Method> methods = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
getMethodListByParameters(boolean.class, new Class[] {});
// Try to look for boolean methods
if (methods.size() == 2) {
isMinecraftAsync = methods.get(1);
@@ -460,7 +454,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
}
}
}
@Override
public int compareTo(AsyncMarker o) {
if (o == null)
@@ -468,7 +462,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
else
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
}
@Override
public boolean equals(Object other) {
// Standard equals
@@ -479,7 +473,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
else
return false;
}
@Override
public int hashCode() {
return Longs.hashCode(getNewSendingIndex());
@@ -17,13 +17,9 @@
package com.comphenix.protocol.async;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.*;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import org.bukkit.plugin.Plugin;
/**
* Represents a NO OPERATION listener.
@@ -2,16 +2,16 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
@@ -24,81 +24,79 @@ import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.PriorityBlockingQueue;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.reflect.FieldAccessException;
import org.bukkit.entity.Player;
/**
* Represents packets ready to be transmitted to a client.
*
* @author Kristian
*/
abstract class PacketSendingQueue {
public static final ReportType REPORT_DROPPED_PACKET = new ReportType("Warning: Dropped packet index %s of type %s.");
public static final int INITIAL_CAPACITY = 10;
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
// Asynchronous packet sending
private Executor asynchronousSender;
// Whether or not packet transmission must occur on a specific thread
private final boolean notThreadSafe;
private final PriorityBlockingQueue<PacketEventHolder> sendingQueue;
// Asynchronous packet sending
private final Executor asynchronousSender;
// Whether or not we've run the cleanup procedure
private boolean cleanedUp = false;
/**
* Create a packet sending queue.
*
* @param notThreadSafe - whether or not to synchronize with the main thread or a background thread.
*/
public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) {
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
this.sendingQueue = new PriorityBlockingQueue<>(INITIAL_CAPACITY);
this.notThreadSafe = notThreadSafe;
this.asynchronousSender = asynchronousSender;
}
/**
* Number of packet events in the queue.
*
* @return The number of packet events in the queue.
*/
public int size() {
return sendingQueue.size();
}
/**
* Enqueue a packet for sending.
* Enqueue a packet for sending.
*
* @param packet - packet to queue.
*/
public void enqueue(PacketEvent packet) {
sendingQueue.add(new PacketEventHolder(packet));
}
/**
* Invoked when one of the packets have finished processing.
*
* @param packetUpdated - the packet that has now been updated.
* @param onMainThread - whether or not this is occuring on the main thread.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
AsyncMarker marker = packetUpdated.getAsyncMarker();
// Should we reorder the event?
if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex() && !marker.hasExpired()) {
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
// "Cancel" the original event
packetUpdated.setReadOnly(false);
packetUpdated.setCancelled(true);
// Enqueue the copy with the new sending index
enqueue(copy);
}
// Mark this packet as finished
marker.setProcessed(true);
trySendPackets(onMainThread);
@@ -107,83 +105,85 @@ abstract class PacketSendingQueue {
/***
* Invoked when a list of packet IDs are no longer associated with any listeners.
* @param packetsRemoved - packets that no longer have any listeners.
* @param onMainThread - whether or not this is occuring on the main thread.
* @param onMainThread - whether or not this is occurring on the main thread.
*/
public synchronized void signalPacketUpdate(List<PacketType> packetsRemoved, boolean onMainThread) {
Set<PacketType> lookup = new HashSet<PacketType>(packetsRemoved);
Set<PacketType> lookup = new HashSet<>(packetsRemoved);
// Note that this is O(n), so it might be expensive
for (PacketEventHolder holder : sendingQueue) {
PacketEvent event = holder.getEvent();
if (lookup.contains(event.getPacketType())) {
event.getAsyncMarker().setProcessed(true);
}
}
// This is likely to have changed the situation a bit
trySendPackets(onMainThread);
}
/**
* Attempt to send any remaining packets.
*
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public void trySendPackets(boolean onMainThread) {
// Whether or not to continue sending packets
boolean sending = true;
// Transmit as many packets as we can
while (sending) {
PacketEventHolder holder = sendingQueue.poll();
if (holder != null) {
sending = processPacketHolder(onMainThread, holder);
if (!sending) {
// Add it back again
sendingQueue.add(holder);
}
} else {
// No more packets to send
sending = false;
}
}
}
/**
* Invoked when a packet might be ready for transmission.
*
* @param onMainThread - TRUE if we're on the main thread, FALSE otherwise.
* @param holder - packet container.
* @param holder - packet container.
* @return TRUE to continue sending packets, FALSE otherwise.
*/
private boolean processPacketHolder(boolean onMainThread, final PacketEventHolder holder) {
PacketEvent current = holder.getEvent();
AsyncMarker marker = current.getAsyncMarker();
boolean hasExpired = marker.hasExpired();
// Guard in cause the queue is closed
if (cleanedUp) {
return true;
}
// End condition?
if (marker.isProcessed() || hasExpired) {
if (hasExpired) {
// Notify timeout listeners
onPacketTimeout(current);
// Recompute
marker = current.getAsyncMarker();
hasExpired = marker.hasExpired();
// Could happen due to the timeout listeners
if (!marker.isProcessed() && !hasExpired) {
return false;
}
}
// Is it okay to send the packet?
if (!current.isCancelled() && !hasExpired) {
// Make sure we're on the main thread
@@ -191,65 +191,61 @@ abstract class PacketSendingQueue {
try {
boolean wantAsync = marker.isMinecraftAsync(current);
boolean wantSync = !wantAsync;
// Wait for the next main thread heartbeat if we haven't fulfilled our promise
if (!onMainThread && wantSync) {
return false;
}
// Let's give it what it wants
if (onMainThread && wantAsync) {
asynchronousSender.execute(new Runnable() {
@Override
public void run() {
// We know this isn't on the main thread
processPacketHolder(false, holder);
}
});
// We know this isn't on the main thread
asynchronousSender.execute(() -> processPacketHolder(false, holder));
// Scheduler will do the rest
return true;
}
} catch (FieldAccessException e) {
e.printStackTrace();
// Just drop the packet
return true;
}
}
}
// Silently skip players that have logged out
if (isOnline(current.getPlayer())) {
sendPacket(current);
}
}
}
// Drop the packet
return true;
}
// Add it back and stop sending
return false;
}
/**
* Invoked when a packet has timed out.
*
* @param event - the timed out packet.
*/
protected abstract void onPacketTimeout(PacketEvent event);
private boolean isOnline(Player player) {
return player != null && player.isOnline();
}
/**
* Send every packet, regardless of the processing state.
*/
private void forceSend() {
while (true) {
PacketEventHolder holder = sendingQueue.poll();
if (holder != null) {
sendPacket(holder.getEvent());
} else {
@@ -257,9 +253,10 @@ abstract class PacketSendingQueue {
}
}
}
/**
* Whether or not the packet transmission must synchronize with the main thread.
*
* @return TRUE if it must, FALSE otherwise.
*/
public boolean isSynchronizeMain() {
@@ -268,24 +265,16 @@ abstract class PacketSendingQueue {
/**
* Transmit a packet, if it hasn't already.
*
* @param event - the packet to transmit.
*/
private void sendPacket(PacketEvent event) {
AsyncMarker marker = event.getAsyncMarker();
try {
// Don't send a packet twice
AsyncMarker marker = event.getAsyncMarker();
if (marker != null && !marker.isTransmitted()) {
marker.sendPacket(event);
}
} catch (PlayerLoggedOutException e) {
ProtocolLibrary.getErrorReporter().reportDebug(this, Report.newBuilder(REPORT_DROPPED_PACKET).
messageParam(marker.getOriginalSendingIndex(), event.getPacketType()).
callerParam(event)
);
} catch (IOException e) {
// Just print the error
e.printStackTrace();
@@ -299,7 +288,7 @@ abstract class PacketSendingQueue {
if (!cleanedUp) {
// Note that the cleanup itself will always occur on the main thread
forceSend();
// And we're done
cleanedUp = true;
}
@@ -24,8 +24,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.concurrency.ConcurrentPlayerMap;
import com.comphenix.protocol.error.ErrorReporter;
@@ -33,18 +31,20 @@ import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.SortedPacketListenerList;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.bukkit.entity.Player;
/**
* Contains every sending queue for every player.
*
* @author Kristian
*/
class PlayerSendingHandler {
private ErrorReporter reporter;
private ConcurrentMap<Player, QueueContainer> playerSendingQueues;
private final ErrorReporter reporter;
private final ConcurrentMap<Player, QueueContainer> playerSendingQueues;
// Timeout listeners
private SortedPacketListenerList serverTimeoutListeners;
private SortedPacketListenerList clientTimeoutListeners;
private final SortedPacketListenerList serverTimeoutListeners;
private final SortedPacketListenerList clientTimeoutListeners;
// Asynchronous packet sending
private Executor asynchronousSender;
@@ -58,8 +58,8 @@ class PlayerSendingHandler {
* @author Kristian
*/
private class QueueContainer {
private PacketSendingQueue serverQueue;
private PacketSendingQueue clientQueue;
private final PacketSendingQueue serverQueue;
private final PacketSendingQueue clientQueue;
public QueueContainer() {
// Server packets can be sent concurrently
@@ -174,7 +174,7 @@ class PlayerSendingHandler {
/**
* Immediately send every server packet with the given list of IDs.
* @param types - types of every packet to send immediately.
* @param synchronusOK - whether or not we're running on the main thread.
* @param synchronusOK - whether we're running on the main thread.
*/
public void sendServerPackets(List<PacketType> types, boolean synchronusOK) {
if (!cleaningUp) {
@@ -186,8 +186,7 @@ class PlayerSendingHandler {
/**
* Immediately send every client packet with the given list of IDs.
* @param ids - ID of every packet to send immediately.
* @param synchronusOK - whether or not we're running on the main thread.
* @param synchronusOK - whether we're running on the main thread.
*/
public void sendClientPackets(List<PacketType> types, boolean synchronusOK) {
if (!cleaningUp) {
@@ -199,7 +198,7 @@ class PlayerSendingHandler {
/**
* Send any outstanding server packets.
* @param onMainThread - whether or not this is occuring on the main thread.
* @param onMainThread - whether this is occurring on the main thread.
*/
public void trySendServerPackets(boolean onMainThread) {
for (QueueContainer queue : playerSendingQueues.values()) {
@@ -209,7 +208,7 @@ class PlayerSendingHandler {
/**
* Send any outstanding server packets.
* @param onMainThread - whether or not this is occuring on the main thread.
* @param onMainThread - whether this is occurring on the main thread.
*/
public void trySendClientPackets(boolean onMainThread) {
for (QueueContainer queue : playerSendingQueues.values()) {
@@ -222,7 +221,7 @@ class PlayerSendingHandler {
* @return Every sever packet queue.
*/
public List<PacketSendingQueue> getServerQueues() {
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
List<PacketSendingQueue> result = new ArrayList<>();
for (QueueContainer queue : playerSendingQueues.values())
result.add(queue.getServerQueue());
@@ -234,7 +233,7 @@ class PlayerSendingHandler {
* @return Every client packet queue.
*/
public List<PacketSendingQueue> getClientQueues() {
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
List<PacketSendingQueue> result = new ArrayList<>();
for (QueueContainer queue : playerSendingQueues.values())
result.add(queue.getClientQueue());
@@ -17,13 +17,12 @@
package com.comphenix.protocol.async;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Queue;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
/**
@@ -8,7 +8,6 @@ import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Ticker;
@@ -49,19 +48,14 @@ public class ExpireHashMap<K, V> {
}
}
private Map<K, ExpireEntry> keyLookup = new HashMap<K, ExpireEntry>();
private PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<ExpireEntry>();
private final Map<K, ExpireEntry> keyLookup = new HashMap<>();
private final PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<>();
// View of keyLookup with direct values
private Map<K, V> valueView = Maps.transformValues(keyLookup, new Function<ExpireEntry, V>() {
@Override
public V apply(ExpireEntry entry) {
return entry.expireValue;
}
});
private final Map<K, V> valueView = Maps.transformValues(keyLookup, entry -> entry.expireValue);
// Supplied by the constructor
private Ticker ticker;
private final Ticker ticker;
/**
* Construct a new hash map where each entry may expire at a given time.
@@ -1,10 +1,10 @@
package com.comphenix.protocol.collections;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
/**
* Represents a very quick integer-based lookup map, with a fixed key space size.
@@ -16,15 +16,6 @@ public class IntegerMap<T> {
private T[] array;
private int size;
/**
* Construct a new integer map.
* @param <T> Parameter type
* @return A new integer map.
*/
public static <T> IntegerMap<T> newMap() {
return new IntegerMap<T>();
}
/**
* Construct a new integer map with a default capacity.
*/
@@ -128,7 +119,7 @@ public class IntegerMap<T> {
* @return The Integer map.
*/
public Map<Integer, Object> toMap() {
Map<Integer, Object> map = Maps.newHashMap();
final Map<Integer, Object> map = new HashMap<>();
for (int i = 0; i < array.length; i++) {
if (array[i] != null) {
@@ -2,21 +2,25 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.concurrency;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -24,81 +28,78 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.Iterables;
/**
* A thread-safe implementation of a listener multimap.
*
*
* @author Kristian
*/
public abstract class AbstractConcurrentListenerMultimap<TListener> {
public abstract class AbstractConcurrentListenerMultimap<T> {
// The core of our map
private ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> mapListeners;
private final ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<T>>> mapListeners;
public AbstractConcurrentListenerMultimap() {
mapListeners = new ConcurrentHashMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
this.mapListeners = new ConcurrentHashMap<>();
}
/**
* Adds a listener to its requested list of packet receivers.
* @param listener - listener with a list of packets to receive notifications for.
*
* @param listener - listener with a list of packets to receive notifications for.
* @param whitelist - the packet whitelist to use.
*/
public void addListener(TListener listener, ListeningWhitelist whitelist) {
PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
public void addListener(T listener, ListeningWhitelist whitelist) {
PrioritizedListener<T> prioritized = new PrioritizedListener<>(listener, whitelist.getPriority());
for (PacketType type : whitelist.getTypes()) {
addListener(type, prioritized);
this.addListener(type, prioritized);
}
}
// Add the listener to a specific packet notifcation list
private void addListener(PacketType type, PrioritizedListener<TListener> listener) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
private void addListener(PacketType type, PrioritizedListener<T> listener) {
SortedCopyOnWriteArray<PrioritizedListener<T>> list = this.mapListeners.get(type);
// We don't want to create this for every lookup
if (list == null) {
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
// which is a essential feature for our purposes.
final SortedCopyOnWriteArray<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>();
final SortedCopyOnWriteArray<PrioritizedListener<T>> value = new SortedCopyOnWriteArray<PrioritizedListener<T>>();
// We may end up creating multiple multisets, but we'll agree on which to use
list = mapListeners.putIfAbsent(type, value);
list = this.mapListeners.putIfAbsent(type, value);
if (list == null) {
list = value;
}
}
// Thread safe
list.add(listener);
}
/**
* Removes the given listener from the packet event list.
* @param listener - listener to remove.
*
* @param listener - listener to remove.
* @param whitelist - the packet whitelist that was used.
* @return Every packet ID that was removed due to no listeners.
*/
public List<PacketType> removeListener(TListener listener, ListeningWhitelist whitelist) {
public List<PacketType> removeListener(T listener, ListeningWhitelist whitelist) {
List<PacketType> removedPackets = new ArrayList<PacketType>();
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
for (PacketType type : whitelist.getTypes()) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
SortedCopyOnWriteArray<PrioritizedListener<T>> list = this.mapListeners.get(type);
// Remove any listeners
if (list != null) {
// Don't remove from newly created lists
if (list.size() > 0) {
// Remove this listener. Note that priority is generally ignored.
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
list.remove(new PrioritizedListener<T>(listener, whitelist.getPriority()));
if (list.size() == 0) {
mapListeners.remove(type);
this.mapListeners.remove(type);
removedPackets.add(type);
}
}
@@ -107,38 +108,41 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
}
return removedPackets;
}
/**
* Retrieve the registered listeners, in order from the lowest to the highest priority.
* <p>
* The returned list is thread-safe and doesn't require synchronization.
*
* @param type - packet type.
* @return Registered listeners.
*/
public Collection<PrioritizedListener<TListener>> getListener(PacketType type) {
return mapListeners.get(type);
public Collection<PrioritizedListener<T>> getListener(PacketType type) {
return this.mapListeners.get(type);
}
/**
* Retrieve every listener.
*
* @return Every listener.
*/
public Iterable<PrioritizedListener<TListener>> values() {
return Iterables.concat(mapListeners.values());
public Iterable<PrioritizedListener<T>> values() {
return Iterables.concat(this.mapListeners.values());
}
/**
* Retrieve every registered packet type:
*
* @return Registered packet type.
*/
public Set<PacketType> keySet() {
return mapListeners.keySet();
return this.mapListeners.keySet();
}
/**
* Remove all packet listeners.
*/
protected void clearListeners() {
mapListeners.clear();
this.mapListeners.clear();
}
}
@@ -7,9 +7,9 @@ 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.Bukkit;
import org.bukkit.entity.Player;
import java.util.AbstractMap;
@@ -73,7 +73,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* @return Concurrent player map.
*/
public static <T> ConcurrentPlayerMap<T> usingAddress() {
return new ConcurrentPlayerMap<T>(PlayerKey.ADDRESS);
return new ConcurrentPlayerMap<>(PlayerKey.ADDRESS);
}
/**
@@ -82,7 +82,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* @return Concurrent player map.
*/
public static <T> ConcurrentPlayerMap<T> usingName() {
return new ConcurrentPlayerMap<T>(PlayerKey.NAME);
return new ConcurrentPlayerMap<>(PlayerKey.NAME);
}
/**
@@ -108,7 +108,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* @return The value map.
*/
private ConcurrentMap<Object, TValue> createValueMap() {
return Maps.newConcurrentMap();
return new ConcurrentHashMap<>();
}
/**
@@ -162,7 +162,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* @return The player with the given key, or NULL if not found.
*/
private Player findOnlinePlayer(Object key) {
for (Player player : Util.getOnlinePlayers()) {
for (Player player : Bukkit.getOnlinePlayers()) {
if (key.equals(keyMethod.apply(player))) {
return player;
}
@@ -1,116 +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.concurrency;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Represents a very quick integer set that uses a lookup table to store membership.
* <p>
* This class is intentionally missing a size method.
* @author Kristian
*/
public class IntegerSet {
private final boolean[] array;
/**
* Initialize a lookup table with the given maximum number of elements.
* <p>
* This creates a set for elements in the range [0, count).
* <p>
* Formally, the current set must be a subset of [0, 1, 2, ..., count - 1].
* @param maximumCount - maximum element value and count.
*/
public IntegerSet(int maximumCount) {
this.array = new boolean[maximumCount];
}
/**
* Initialize a lookup table with a given maximum and value list.
* <p>
* The provided elements must be in the range [0, count).
* @param maximumCount - the maximum element value and count.
* @param values - the elements to add to the set.
*/
public IntegerSet(int maximumCount, Collection<Integer> values) {
this.array = new boolean[maximumCount];
addAll(values);
}
/**
* Determine whether or not the given element exists in the set.
* @param element - the element to check. Must be in the range [0, count).
* @return TRUE if the given element exists, FALSE otherwise.
*/
public boolean contains(int element) {
return array[element];
}
/**
* Add the given element to the set, or do nothing if it already exists.
* @param element - element to add.
* @throws ArrayIndexOutOfBoundsException If the given element is not in the range [0, count).
*/
public void add(int element) {
array[element] = true;
}
/**
* Add the given collection of elements to the set.
* @param packets - elements to add.
*/
public void addAll(Collection<Integer> packets) {
for (Integer id : packets) {
add(id);
}
}
/**
* Remove the given element from the set, or do nothing if it's already removed.
* @param element - element to remove.
*/
public void remove(int element) {
// We don't actually care if the caller tries to remove an element outside the valid set
if (element >= 0 && element < array.length)
array[element] = false;
}
/**
* Remove every element from the set.
*/
public void clear() {
Arrays.fill(array, false);
}
/**
* Convert the current IntegerSet to an equivalent HashSet.
* @return The resulting HashSet.
*/
public Set<Integer> toSet() {
Set<Integer> elements = new HashSet<Integer>();
for (int i = 0; i < array.length; i++) {
if (array[i])
elements.add(i);
}
return elements;
}
}
@@ -1,134 +1,133 @@
package com.comphenix.protocol.concurrency;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Represents a concurrent set of packet types.
*
* @author Kristian
*/
public class PacketTypeSet {
private Set<PacketType> types = Collections.newSetFromMap(Maps.<PacketType, Boolean>newConcurrentMap());
private Set<Class<?>> classes = Collections.newSetFromMap(Maps.<Class<?>, Boolean>newConcurrentMap());
private final Set<PacketType> types;
private final Set<Class<?>> classes;
public PacketTypeSet() {
// Do nothing
this.types = new HashSet<>(16, 0.9f);
this.classes = new HashSet<>(16, 0.9f);
}
public PacketTypeSet(Collection<? extends PacketType> values) {
for (PacketType type : values) {
addType(type);
}
this.types = new HashSet<>(values.size(), 0.9f);
this.classes = new HashSet<>(values.size(), 0.9f);
this.addAll(values);
}
/**
* Add a particular type to the set.
*
* @param type - the type to add.
*/
public synchronized void addType(PacketType type) {
Class<?> packetClass = getPacketClass(type);
types.add(Preconditions.checkNotNull(type, "type cannot be NULL."));
public void addType(PacketType type) {
this.types.add(type);
Class<?> packetClass = type.getPacketClass();
if (packetClass != null) {
classes.add(getPacketClass(type));
this.classes.add(packetClass);
}
}
/**
* Add the given types to the set of packet types.
*
* @param types - the types to add.
*/
public synchronized void addAll(Iterable<? extends PacketType> types) {
public void addAll(Iterable<? extends PacketType> types) {
for (PacketType type : types) {
addType(type);
this.addType(type);
}
}
/**
* Remove a particular type to the set.
*
* @param type - the type to remove.
*/
public synchronized void removeType(PacketType type) {
Class<?> packetClass = getPacketClass(type);
types.remove(Preconditions.checkNotNull(type, "type cannot be NULL."));
public void removeType(PacketType type) {
this.types.remove(type);
Class<?> packetClass = type.getPacketClass();
if (packetClass != null) {
classes.remove(getPacketClass(type));
this.classes.remove(packetClass);
}
}
/**
* Remove the given types from the set.
*
* @param types Types to remove
*/
public synchronized void removeAll(Iterable<? extends PacketType> types) {
public void removeAll(Iterable<? extends PacketType> types) {
for (PacketType type : types) {
removeType(type);
this.removeType(type);
}
}
/**
* Retrieve the packet class associated with a particular type.
* @param type - the packet type.
* @return The associated packet type.
*/
protected Class<?> getPacketClass(PacketType type) {
return PacketRegistry.getPacketClassFromType(type);
}
/**
* Determine if the given packet type exists in the set.
*
* @param type - the type to find.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean contains(PacketType type) {
return types.contains(type);
return this.types.contains(type);
}
/**
* Determine if a packet type with the given packet class exists in the set.
*
* @param packetClass - the class to find.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean contains(Class<?> packetClass) {
return classes.contains(packetClass);
return this.classes.contains(packetClass);
}
/**
* Determine if the type of a packet is in the current set.
*
* @param packet - the packet.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean containsPacket(Object packet) {
if (packet == null)
return false;
return classes.contains(packet.getClass());
return packet != null && this.classes.contains(packet.getClass());
}
/**
* Retrieve a view of this packet type set.
*
* @return The packet type values.
*/
public Set<PacketType> values() {
return types;
return ImmutableSet.copyOf(this.types);
}
/**
* Retrieve the number of entries in the set.
*
* @return The number of entries.
*/
public int size() {
return types.size();
return this.types.size();
}
public synchronized void clear() {
types.clear();
classes.clear();
public void clear() {
this.types.clear();
this.classes.clear();
}
}
@@ -2,243 +2,236 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.concurrency;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
/**
* An implicitly sorted array list that preserves insertion order and maintains duplicates.
*
* @param <T> - type of the elements in the list.
*/
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Collection<T> {
// Prevent reordering
private volatile List<T> list;
/**
* Construct an empty sorted array.
*/
public SortedCopyOnWriteArray() {
list = new ArrayList<T>();
this.list = new ArrayList<>();
}
/**
* Create a sorted array from the given list. The elements will be automatically sorted.
*
* @param wrapped - the collection whose elements are to be placed into the list.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped) {
this.list = new ArrayList<T>(wrapped);
this.list = new ArrayList<>(wrapped);
Collections.sort(this.list);
}
/**
* Create a sorted array from the given list.
* @param wrapped - the collection whose elements are to be placed into the list.
* @param sort - TRUE to automatically sort the collection, FALSE if it is already sorted.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped, boolean sort) {
this.list = new ArrayList<T>(wrapped);
if (sort) {
Collections.sort(list);
}
}
/**
* Inserts the given element in the proper location.
*
* @param value - element to insert.
*/
@Override
public synchronized boolean add(T value) {
// We use NULL as a special marker, so we don't allow it
if (value == null)
throw new IllegalArgumentException("value cannot be NULL");
List<T> copy = new ArrayList<T>();
// We use NULL as a special marker, so we don't allow it
if (value == null) throw new IllegalArgumentException("value cannot be NULL");
List<T> copy = new ArrayList<>();
boolean inserted = false;
for (T element : this.list) {
// If the value is now greater than the current element, it should be placed right before it
if (!inserted && value.compareTo(element) < 0) {
copy.add(value);
inserted = true;
}
copy.add(element);
}
// Don't forget to add it
if (!inserted) {
copy.add(value);
}
this.list = copy;
return true;
}
for (T element : list) {
// If the value is now greater than the current element, it should be placed right before it
if (value != null && value.compareTo(element) < 0) {
copy.add(value);
value = null;
}
copy.add(element);
}
// Don't forget to add it
if (value != null)
copy.add(value);
list = copy;
return true;
}
@Override
public synchronized boolean addAll(Collection<? extends T> values) {
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
public synchronized boolean addAll(Collection<? extends T> values) {
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0) {
return false;
List<T> copy = new ArrayList<T>();
// Insert the new content and sort it
copy.addAll(list);
copy.addAll(values);
Collections.sort(copy);
list = copy;
return true;
}
/**
* Removes from the list by making a new list with every element except the one given.
* <p>
* Objects will be compared using the given objects equals() method.
* @param value - value to remove.
*/
@Override
public synchronized boolean remove(Object value) {
List<T> copy = new ArrayList<T>();
boolean result = false;
// Note that there's not much to be gained from using BinarySearch, as we
// have to copy (and thus read) the entire list regardless.
// Copy every element except the one given to us.
for (T element : list) {
if (!Objects.equal(value, element)) {
copy.add(element);
} else {
result = true;
}
}
}
List<T> copy = new ArrayList<>(this.list);
// Insert the new content and sort it
copy.addAll(values);
Collections.sort(copy);
this.list = copy;
return true;
}
/**
* Removes from the list by making a new list with every element except the one given.
* <p>
* Objects will be compared using the given objects equals() method.
*
* @param value - value to remove.
*/
@Override
public synchronized boolean remove(Object value) {
List<T> copy = new ArrayList<>();
boolean result = false;
// Note that there's not much to be gained from using BinarySearch, as we
// have to copy (and thus read) the entire list regardless.
// Copy every element except the one given to us.
for (T element : this.list) {
if (!Objects.equal(value, element)) {
copy.add(element);
} else {
result = true;
}
}
this.list = copy;
return result;
}
list = copy;
return result;
}
@Override
public boolean removeAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0) {
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
}
List<T> copy = new ArrayList<>(this.list);
copy.removeAll(values);
list = copy;
this.list = copy;
return true;
}
@Override
public boolean retainAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
if (values.isEmpty()) return false;
List<T> copy = new ArrayList<>(this.list);
copy.removeAll(values);
list = copy;
this.list = copy;
return true;
}
/**
* Removes from the list by making a copy of every element except the one with the given index.
* @param index - index of the element to remove.
*/
public synchronized void remove(int index) {
List<T> copy = new ArrayList<T>(list);
copy.remove(index);
list = copy;
}
/**
* Retrieves an element by index.
* @param index - index of element to retrieve.
* @return The element at the given location.
*/
public T get(int index) {
return list.get(index);
}
/**
* Retrieve the size of the list.
* @return Size of the list.
*/
public int size() {
return list.size();
}
/**
* Retrieves an iterator over the elements in the given list.
* Warning: No not attempt to remove elements using the iterator.
*/
public Iterator<T> iterator() {
return Iterables.unmodifiableIterable(list).iterator();
/**
* Removes from the list by making a copy of every element except the one with the given index.
*
* @param index - index of the element to remove.
*/
public synchronized void remove(int index) {
List<T> copy = new ArrayList<>(this.list);
copy.remove(index);
this.list = copy;
}
/**
* Retrieves an element by index.
*
* @param index - index of element to retrieve.
* @return The element at the given location.
*/
public T get(int index) {
return this.list.get(index);
}
/**
* Retrieve the size of the list.
*
* @return Size of the list.
*/
public int size() {
return this.list.size();
}
/**
* Retrieves an iterator over the elements in the given list. Warning: No not attempt to remove elements using the
* iterator.
*/
public Iterator<T> iterator() {
return Iterables.unmodifiableIterable(this.list).iterator();
}
@Override
public void clear() {
list = new ArrayList<T>();
this.list = new ArrayList<>();
}
@Override
public boolean contains(Object value) {
return list.contains(value);
return this.list.contains(value);
}
@Override
public boolean containsAll(Collection<?> values) {
return list.containsAll(values);
return this.list.containsAll(values);
}
@Override
public boolean isEmpty() {
return list.isEmpty();
return this.list.isEmpty();
}
@Override
public Object[] toArray() {
return list.toArray();
return this.list.toArray();
}
@SuppressWarnings("hiding")
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
return this.list.toArray(a);
}
@Override
public String toString() {
return list.toString();
return this.list.toString();
}
}
@@ -2,11 +2,11 @@ package com.comphenix.protocol.error;
import java.io.PrintStream;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import com.comphenix.protocol.reflect.PrettyPrinter;
import org.bukkit.plugin.Plugin;
/**
* Represents a basic error reporter that prints error reports to the standard error stream.
* <p>
@@ -1,9 +1,9 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import org.bukkit.plugin.Plugin;
/**
* Construct an error reporter that delegates to another error reporter.
* @author Kristian
@@ -30,11 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.collections.ExpireHashMap;
import com.comphenix.protocol.error.Report.ReportBuilder;
@@ -43,6 +39,11 @@ import com.comphenix.protocol.reflect.PrettyPrinter;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Primitives;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
/**
* Internal class used to handle exceptions.
*
@@ -89,7 +90,7 @@ public class DetailedErrorReporter implements ErrorReporter {
// Reports to ignore
private ExpireHashMap<Report, Boolean> rateLimited = new ExpireHashMap<Report, Boolean>();
private Object rateLock = new Object();
private final Object rateLock = new Object();
/**
* Create a default error reporting system.
@@ -202,14 +203,14 @@ public class DetailedErrorReporter implements ErrorReporter {
// See if we should print the full error
if (errorCount < getMaxErrorCount()) {
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " +
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occurred in " +
methodName + " for " + pluginName, error);
return true;
} else {
// Nope - only print the error count occationally
// Nope - only print the error count occasionally
if (isPowerOfTwo(errorCount)) {
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " +
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occurred in " +
methodName + " for " + pluginName, error);
}
return false;
@@ -390,9 +391,9 @@ public class DetailedErrorReporter implements ErrorReporter {
writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
// Inform of this occurrence
if (ERROR_PERMISSION != null) {
if (ERROR_PERMISSION != null && ProtocolLibrary.getConfig().isChatWarnings()) {
Bukkit.getServer().broadcast(
String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender),
String.format("Error %s (%s) occurred in %s.", report.getReportMessage(), report.getException(), sender),
ERROR_PERMISSION
);
}
@@ -17,10 +17,10 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import org.bukkit.plugin.Plugin;
/**
* Represents an object that can forward an error {@link Report} to the display and permanent storage.
*
@@ -4,11 +4,11 @@ import java.io.File;
import java.net.URLDecoder;
import java.security.CodeSource;
import com.google.common.base.Preconditions;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import com.google.common.base.Preconditions;
public final class PluginContext {
// Determine plugin folder
private static File pluginFolder;
@@ -1,10 +1,9 @@
package com.comphenix.protocol.error;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/**
* Represents a error or warning report.
*
@@ -1,10 +1,10 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import com.google.common.base.Joiner;
import org.bukkit.plugin.Plugin;
/**
* Represents an error reporter that rethrows every exception instead.
* @author Kristian
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,48 @@
package com.comphenix.protocol.events;
import java.util.Optional;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.wrappers.Converters;
public class InternalStructure extends AbstractStructure {
protected InternalStructure(Object handle, StructureModifier<Object> structureModifier) {
super(handle, structureModifier);
}
protected static final EquivalentConverter<InternalStructure> CONVERTER = new EquivalentConverter<InternalStructure>() {
@Override
public Object getGeneric(InternalStructure specific) {
return specific.handle;
}
@Override
public InternalStructure getSpecific(Object generic) {
return new InternalStructure(generic, new StructureModifier<>(generic.getClass()).withTarget(generic));
}
@Override
public Class<InternalStructure> getSpecificType() {
return InternalStructure.class;
}
};
public static EquivalentConverter<InternalStructure> getConverter() {
return CONVERTER;
}
public StructureModifier<InternalStructure> getStructures() {
return structureModifier.withType(Object.class, CONVERTER);
}
public StructureModifier<Optional<InternalStructure>> getOptionalStructures() {
return structureModifier.withType(Optional.class, Converters.optional(CONVERTER));
}
@Override
public String toString() {
return "InternalStructure[handle=" + handle + " (" + handle.getClass().getSimpleName() + ")]";
}
}
@@ -2,31 +2,32 @@ package com.comphenix.protocol.events;
import com.comphenix.protocol.injector.GamePhase;
/**
* Represents additional options a listener may require.
*
*
* @author Kristian
*/
public enum ListenerOptions {
/**
* Retrieve the serialized client packet as it appears on the network stream.
*/
INTERCEPT_INPUT_BUFFER,
/**
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when
* a packet ID is known to be transmitted during login.
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when a packet ID is
* known to be transmitted during login.
*/
DISABLE_GAMEPHASE_DETECTION,
/**
* Do not verify that the owning plugin has a vaid plugin.yml.
*/
SKIP_PLUGIN_VERIFIER,
/**
* Notify ProtocolLib that {@link PacketListener#onPacketSending(PacketEvent)} is thread safe.
*/
ASYNC;
ASYNC,
/**
* Notify ProtocolLib that {@link PacketListener#onPacketReceiving(PacketEvent)} must be executed on the main server
* thread.
*/
SYNC
}
@@ -17,24 +17,20 @@
package com.comphenix.protocol.events;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.*;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.google.common.base.Objects;
import com.google.common.collect.Sets;
/**
* Determines which packets will be observed by a listener, and with what priority.
*
* @author Kristian
*/
public class ListeningWhitelist {
/**
* A whitelist with no packets - indicates that the listener shouldn't observe any packets.
*/
@@ -44,95 +40,87 @@ public class ListeningWhitelist {
private final GamePhase gamePhase;
private final Set<ListenerOptions> options;
private final Set<PacketType> types;
// Cache whitelist
private transient Set<Integer> intWhitelist;
private ListeningWhitelist(Builder builder) {
this.priority = builder.priority;
this.types = builder.types;
this.gamePhase = builder.gamePhase;
this.options = builder.options;
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - set of IDs to observe/enable.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist) {
this(priority, whitelist, GamePhase.PLAYING);
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - set of IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist, GamePhase gamePhase) {
private ListeningWhitelist(ListenerPriority priority) {
this.priority = priority;
this.types = PacketRegistry.toPacketTypes(safeSet(whitelist));
this.gamePhase = gamePhase;
this.options = EnumSet.noneOf(ListenerOptions.class);
}
/**
* Creates a packet whitelist of a given priority for a list of packets.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) {
this.priority = priority;
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
this.types = new HashSet<>();
this.gamePhase = GamePhase.PLAYING;
this.options = EnumSet.noneOf(ListenerOptions.class);
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
* Determine if the given whitelist is empty or not.
*
* @param whitelist - the whitelist to test.
* @return TRUE if the whitelist is empty, FALSE otherwise.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase) {
this.priority = priority;
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
this.gamePhase = gamePhase;
this.options = EnumSet.noneOf(ListenerOptions.class);
public static boolean isEmpty(ListeningWhitelist whitelist) {
if (whitelist == EMPTY_WHITELIST) {
return true;
} else if (whitelist == null) {
return true;
} else {
return whitelist.getTypes().isEmpty();
}
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs and options.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
* @param options - every special option associated with this whitelist.
* Construct a new builder of whitelists.
*
* @return New whitelist builder.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase, ListenerOptions... options) {
this.priority = priority;
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
this.gamePhase = gamePhase;
this.options = safeEnumSet(Arrays.asList(options), ListenerOptions.class);
public static Builder newBuilder() {
return new Builder(null);
}
/**
* Construct a new builder of whitelists initialized to the same values as the template.
*
* @param template - the template object.
* @return New whitelist builder.
*/
public static Builder newBuilder(ListeningWhitelist template) {
return new Builder(template);
}
/**
* Construct a copy of a given enum.
*
* @param options - the options to copy, or NULL to indicate the empty set.
* @return A copy of the enum set.
*/
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
if (options != null && !options.isEmpty()) {
return EnumSet.copyOf(options);
} else {
return EnumSet.noneOf(enumClass);
}
}
/**
* Construct a copy of a given set.
*
* @param set - the set to copy.
* @return The copied set.
*/
private static <T> Set<T> safeSet(Collection<T> set) {
if (set != null) {
return new HashSet<>(set);
} else {
return Collections.emptySet();
}
}
/**
* Whether or not this whitelist has any enabled packets.
*
* @return TRUE if there are any packets, FALSE otherwise.
*/
public boolean isEnabled() {
@@ -141,43 +129,34 @@ public class ListeningWhitelist {
/**
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
*
* @return Execution order in terms of priority.
*/
public ListenerPriority getPriority() {
return priority;
}
/**
* Retrieves the list of packets that will be observed by the listeners.
* <p>
* Deprecated: Use {@link #getTypes()} instead.
* @return Packet whitelist.
*/
@Deprecated
public Set<Integer> getWhitelist() {
if (intWhitelist == null)
intWhitelist = PacketRegistry.toLegacy(types);
return intWhitelist;
}
/**
* Retrieves a set of the packets that will be observed by the listeners.
*
* @return Packet whitelist.
*/
public Set<PacketType> getTypes() {
return types;
}
/**
* Retrieve which game phase this listener is active under.
*
* @return The active game phase.
*/
public GamePhase getGamePhase() {
return gamePhase;
}
/**
* Retrieve every special option associated with this whitelist.
*
* @return Every special option.
*/
public Set<ListenerOptions> getOptions() {
@@ -189,37 +168,6 @@ public class ListeningWhitelist {
return Objects.hashCode(priority, types, gamePhase, options);
}
/**
* Determine if any of the given IDs can be found in the whitelist.
* @param whitelist - whitelist to test.
* @param idList - list of packet IDs to find.
* @return TRUE if any of the packets in the list can be found in the whitelist, FALSE otherwise.
*/
public static boolean containsAny(ListeningWhitelist whitelist, int... idList) {
if (whitelist != null) {
for (int i = 0; i < idList.length; i++) {
if (whitelist.getWhitelist().contains(idList[i]))
return true;
}
}
return false;
}
/**
* Determine if the given whitelist is empty or not.
* @param whitelist - the whitelist to test.
* @return TRUE if the whitelist is empty, FALSE otherwise.
*/
public static boolean isEmpty(ListeningWhitelist whitelist) {
if (whitelist == EMPTY_WHITELIST)
return true;
else if (whitelist == null)
return true;
else
return whitelist.getTypes().isEmpty();
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof ListeningWhitelist) {
@@ -235,67 +183,30 @@ public class ListeningWhitelist {
@Override
public String toString() {
if (this == EMPTY_WHITELIST)
if (this == EMPTY_WHITELIST) {
return "EMPTY_WHITELIST";
else
return "ListeningWhitelist[priority=" + priority + ", packets=" + types + ", gamephase=" + gamePhase + ", options=" + options + "]";
}
/**
* Construct a new builder of whitelists.
* @return New whitelist builder.
*/
public static Builder newBuilder() {
return new Builder(null);
}
/**
* Construct a new builder of whitelists initialized to the same values as the template.
* @param template - the template object.
* @return New whitelist builder.
*/
public static Builder newBuilder(ListeningWhitelist template) {
return new Builder(template);
}
/**
* Construct a copy of a given enum.
* @param options - the options to copy, or NULL to indicate the empty set.
* @return A copy of the enum set.
*/
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
if (options != null && !options.isEmpty()) {
return EnumSet.copyOf(options);
} else {
return EnumSet.noneOf(enumClass);
return "ListeningWhitelist[priority=" + priority + ", packets=" + types + ", gamephase=" + gamePhase
+ ", options=" + options + "]";
}
}
/**
* Construct a copy of a given set.
* @param list - the set to copy.
* @return The copied set.
*/
private static <T> Set<T> safeSet(Collection<T> set) {
if (set != null)
return Sets.newHashSet(set);
else
return Collections.emptySet();
}
/**
* Represents a builder of whitelists.
*
* @author Kristian
*/
public static class Builder {
// Default values
private ListenerPriority priority = ListenerPriority.NORMAL;
private Set<PacketType> types = Sets.newHashSet();
private Set<PacketType> types = new HashSet<>();
private GamePhase gamePhase = GamePhase.PLAYING;
private Set<ListenerOptions> options = Sets.newHashSet();
private Set<ListenerOptions> options = new HashSet<>();
/**
* Construct a new listening whitelist template.
*
* @param template - the template.
*/
private Builder(ListeningWhitelist template) {
@@ -306,9 +217,10 @@ public class ListeningWhitelist {
options(template.getOptions());
}
}
/**
* Set the priority to use when constructing new whitelists.
*
* @param priority - the priority.
* @return This builder, for chaining.
*/
@@ -316,70 +228,64 @@ public class ListeningWhitelist {
this.priority = priority;
return this;
}
/**
* Set the priority of the whitelist to monitor.
*
* @return This builder, for chaining.
*/
public Builder monitor() {
return priority(ListenerPriority.MONITOR);
}
/**
* Set the priority of the whitelist to normal.
*
* @return This builder, for chaining.
*/
public Builder normal() {
return priority(ListenerPriority.NORMAL);
}
/**
* Set the priority of the whitelist to lowest.
*
* @return This builder, for chaining.
*/
public Builder lowest() {
return priority(ListenerPriority.LOWEST);
}
/**
* Set the priority of the whitelist to low.
*
* @return This builder, for chaining.
*/
public Builder low() {
return priority(ListenerPriority.LOW);
}
/**
* Set the priority of the whitelist to highest.
*
* @return This builder, for chaining.
*/
public Builder highest() {
return priority(ListenerPriority.HIGHEST);
}
/**
* Set the priority of the whitelist to high.
*
* @return This builder, for chaining.
*/
public Builder high() {
return priority(ListenerPriority.HIGH);
}
/**
* Set the whitelist of packet IDs to copy when constructing new whitelists.
* <p>
* Deprecated: Use {@link #types(Collection)} instead.
* @param whitelist - the whitelist of packets.
* @return This builder, for chaining.
*/
@Deprecated
public Builder whitelist(Collection<Integer> whitelist) {
this.types = PacketRegistry.toPacketTypes(safeSet(whitelist));
return this;
}
/**
* Set the whitelist of packet types to copy when constructing new whitelists.
*
* @param types - the whitelist of packets.
* @return This builder, for chaining.
*/
@@ -387,9 +293,10 @@ public class ListeningWhitelist {
this.types = safeSet(Sets.newHashSet(types));
return this;
}
/**
* Set the whitelist of packet types to copy when constructing new whitelists.
*
* @param types - the whitelist of packets.
* @return This builder, for chaining.
*/
@@ -397,9 +304,10 @@ public class ListeningWhitelist {
this.types = safeSet(types);
return this;
}
/**
* Set the gamephase to use when constructing new whitelists.
*
* @param gamePhase - the gamephase.
* @return This builder, for chaining.
*/
@@ -407,16 +315,19 @@ public class ListeningWhitelist {
this.gamePhase = gamePhase;
return this;
}
/**
* Set the gamephase to {@link GamePhase#BOTH}.
*
* @return This builder, for chaining.
*/
public Builder gamePhaseBoth() {
return gamePhase(GamePhase.BOTH);
}
/**
* Set the options to copy when constructing new whitelists.
*
* @param options - the options.
* @return This builder, for chaining.
*/
@@ -424,9 +335,10 @@ public class ListeningWhitelist {
this.options = safeSet(options);
return this;
}
/**
* Set the options to copy when constructing new whitelists.
*
* @param options - the options.
* @return This builder, for chaining.
*/
@@ -434,9 +346,10 @@ public class ListeningWhitelist {
this.options = safeSet(options);
return this;
}
/**
* Set the options to copy when constructing new whitelists.
*
* @param serverOptions - the options array.
* @return This builder, for chaining.
*/
@@ -444,32 +357,36 @@ public class ListeningWhitelist {
this.options = safeSet(Sets.newHashSet(serverOptions));
return this;
}
/**
* Options to merge into the current set of options.
*
* @param serverOptions - the options array.
* @return This builder, for chaining.
*/
public Builder mergeOptions(ListenerOptions... serverOptions) {
return mergeOptions(Arrays.asList(serverOptions));
}
/**
* Options to merge into the current set of options.
*
* @param serverOptions - the options array.
* @return This builder, for chaining.
*/
public Builder mergeOptions(Collection<ListenerOptions> serverOptions) {
if (options == null)
if (options == null) {
return options(serverOptions);
}
// Merge the options
this.options.addAll(serverOptions);
return this;
}
/**
* Construct a new whitelist from the values in this builder.
*
* @return The new whitelist.
*/
public ListeningWhitelist build() {
@@ -2,95 +2,76 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.events;
import java.util.logging.Level;
import java.util.Collection;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
import org.bukkit.plugin.Plugin;
/**
* Represents a listener that is notified of every sent and received packet.
*
*
* @author Kristian
*/
public abstract class MonitorAdapter implements PacketListener {
private Plugin plugin;
private ListeningWhitelist sending = ListeningWhitelist.EMPTY_WHITELIST;
private ListeningWhitelist receiving = ListeningWhitelist.EMPTY_WHITELIST;
private final Plugin plugin;
private final ListeningWhitelist sending;
private final ListeningWhitelist receiving;
public MonitorAdapter(Plugin plugin, ConnectionSide side) {
initialize(plugin, side, getLogger(plugin));
}
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
initialize(plugin, side, logger);
}
@SuppressWarnings("deprecation")
private void initialize(Plugin plugin, ConnectionSide side, Logger logger) {
this.plugin = plugin;
// Recover in case something goes wrong
try {
if (side.isForServer())
this.sending = ListeningWhitelist.newBuilder().monitor().types(PacketRegistry.getServerPacketTypes()).gamePhaseBoth().build();
if (side.isForClient())
this.receiving = ListeningWhitelist.newBuilder().monitor().types(PacketRegistry.getClientPacketTypes()).gamePhaseBoth().build();
} catch (FieldAccessException e) {
if (side.isForServer())
this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getRegistry().values(), GamePhase.BOTH);
if (side.isForClient())
this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getRegistry().values(), GamePhase.BOTH);
logger.log(Level.WARNING, "Defaulting to 1.3 packets.", e);
}
// check the connection side and register the packets for the given side
this.sending = side.isForServer() ? buildWhitelist(PacketRegistry.getServerPacketTypes()) : ListeningWhitelist.EMPTY_WHITELIST;
this.receiving = side.isForClient() ? buildWhitelist(PacketRegistry.getClientPacketTypes()) : ListeningWhitelist.EMPTY_WHITELIST;
}
/**
* Retrieve a logger, even if we're running in a CraftBukkit version that doesn't support it.
* @param plugin - the plugin to retrieve.
* @return The logger.
*/
private Logger getLogger(Plugin plugin) {
try {
return plugin.getLogger();
} catch (NoSuchMethodError e) {
return Logger.getLogger("Minecraft");
}
@Deprecated
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
this(plugin, side);
}
private static ListeningWhitelist buildWhitelist(Collection<PacketType> packetTypes) {
return ListeningWhitelist.newBuilder().monitor().gamePhaseBoth().types(packetTypes).build();
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sending;
return this.sending;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receiving;
return this.receiving;
}
@Override
public Plugin getPlugin() {
return plugin;
return this.plugin;
}
@Override
public void onPacketSending(PacketEvent event) {
}
@Override
public void onPacketReceiving(PacketEvent event) {
}
}
@@ -1,421 +1,153 @@
package com.comphenix.protocol.events;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import javax.annotation.Nonnull;
import org.bukkit.entity.Player;
import java.util.*;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.utility.ByteBufferInputStream;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import org.bukkit.entity.Player;
/**
* Marker containing the serialized packet data seen from the network,
* or output handlers that will serialize the current packet.
*
* Marker containing the serialized packet data seen from the network, or output handlers that will serialize the
* current packet.
*
* @author Kristian
*/
public abstract class NetworkMarker {
public static class EmptyBufferMarker extends NetworkMarker {
public EmptyBufferMarker(@Nonnull ConnectionSide side) {
super(side, (byte[]) null, null);
}
public class NetworkMarker {
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
throw new IllegalStateException("Buffer is empty.");
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
@Override
protected DataInputStream addHeader(DataInputStream input, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
}
// Custom network handler
private PriorityQueue<PacketOutputHandler> outputHandlers;
// Post listeners
private List<PacketPostListener> postListeners;
// Post packets
private List<ScheduledPacket> scheduledPackets;
// The input buffer
private ByteBuffer inputBuffer;
private final ConnectionSide side;
// The input data
private final PacketType type;
// Cache serializer too
private StreamSerializer serializer;
/**
* Construct a new network marker.
* @param side - which side this marker belongs to.
* @param inputBuffer - the read serialized packet data.
* @param type - packet type
*/
public NetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer, PacketType type) {
this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
this.inputBuffer = inputBuffer;
this.type = type;
}
private final ConnectionSide side;
// Post-processing of the packet
private Set<PacketPostListener> postListeners;
private Deque<ScheduledPacket> scheduledPackets;
/**
* Construct a new network marker.
* <p>
* The input buffer is only non-null for client-side packets.
*
* @param side - which side this marker belongs to.
* @param inputBuffer - the read serialized packet data.
* @param type - packet type
*/
public NetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer, PacketType type) {
public NetworkMarker(@Nonnull ConnectionSide side, PacketType type) {
this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
this.type = type;
if (inputBuffer != null) {
this.inputBuffer = ByteBuffer.wrap(inputBuffer);
}
}
/**
* Retrieve whether or not this marker belongs to a client or a server side packet.
* @return The side the parent packet belongs to.
*/
public ConnectionSide getSide() {
return side;
}
/**
* Retrieve a utility class for serializing and deserializing Minecraft objects.
* @return Serialization utility class.
*/
public StreamSerializer getSerializer() {
if (serializer == null)
serializer = new StreamSerializer();
return serializer;
}
/**
* Retrieve the serialized packet data (excluding the header by default) from the network input stream.
* <p>
* The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}.
* <p>
* It returns NULL if the packet was transmitted by a plugin locally.
* @return A byte buffer containing the raw packet data read from the network.
*/
public ByteBuffer getInputBuffer() {
return getInputBuffer(true);
}
/**
* Retrieve the serialized packet data from the network input stream.
* <p>
* The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}.
* <p>
* It returns NULL if the packet was transmitted by a plugin locally.
* @param excludeHeader - whether or not to exclude the packet ID header.
* @return A byte buffer containing the raw packet data read from the network.
*/
public ByteBuffer getInputBuffer(boolean excludeHeader) {
if (side.isForServer())
throw new IllegalStateException("Server-side packets have no input buffer.");
if (inputBuffer != null) {
ByteBuffer result = inputBuffer.asReadOnlyBuffer();
try {
if (excludeHeader)
result = skipHeader(result);
else
result = addHeader(result, type);
} catch (IOException e) {
throw new RuntimeException("Cannot skip packet header.", e);
}
return result;
}
return null;
}
/**
* Retrieve the serialized packet data (excluding the header by default) as an input stream.
* <p>
* The data is exactly the same as in {@link #getInputBuffer()}.
* @see #getInputBuffer()
* @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally.
*/
public DataInputStream getInputStream() {
return getInputStream(true);
}
/**
* Retrieve the serialized packet data as an input stream.
* <p>
* The data is exactly the same as in {@link #getInputBuffer()}.
* @see #getInputBuffer()
* @param excludeHeader - whether or not to exclude the packet ID header.
* @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally.
*/
@SuppressWarnings("resource")
public DataInputStream getInputStream(boolean excludeHeader) {
if (side.isForServer())
throw new IllegalStateException("Server-side packets have no input buffer.");
if (inputBuffer == null)
return null;
DataInputStream input = new DataInputStream(
new ByteArrayInputStream(inputBuffer.array())
);
try {
if (excludeHeader)
input = skipHeader(input);
else
input = addHeader(input, type);
} catch (IOException e) {
throw new RuntimeException("Cannot skip packet header.", e);
}
return input;
}
/**
* Whether or not the output handlers have to write a packet header.
* @return TRUE if they do, FALSE otherwise.
*/
public boolean requireOutputHeader() {
return MinecraftReflection.isUsingNetty();
}
/**
* Enqueue the given output handler for managing how the current packet will be written to the network stream.
* <p>
* Note that output handlers are not serialized, as most consumers will probably implement them using anonymous classes.
* It is not safe to serialize anonymous classes, as their name depend on the order in which they are declared in the parent class.
* <p>
* This can only be invoked on server side packet events.
* @param handler - the handler that will take part in serializing the packet.
* @return TRUE if it was added, FALSE if it has already been added.
*/
public boolean addOutputHandler(@Nonnull PacketOutputHandler handler) {
checkServerSide();
Preconditions.checkNotNull(handler, "handler cannot be NULL.");
// Lazy initialization - it's imperative that we save space and time here
if (outputHandlers == null) {
outputHandlers = new PriorityQueue<PacketOutputHandler>(10, new Comparator<PacketOutputHandler>() {
@Override
public int compare(PacketOutputHandler o1, PacketOutputHandler o2) {
return Ints.compare(o1.getPriority().getSlot(), o2.getPriority().getSlot());
}
});
}
return outputHandlers.add(handler);
}
/**
* Remove a given output handler from the serialization queue.
* <p>
* This can only be invoked on server side packet events.
* @param handler - the handler to remove.
* @return TRUE if the handler was removed, FALSE otherwise.
*/
public boolean removeOutputHandler(@Nonnull PacketOutputHandler handler) {
checkServerSide();
Preconditions.checkNotNull(handler, "handler cannot be NULL.");
if (outputHandlers != null) {
return outputHandlers.remove(handler);
}
return false;
}
/**
* Retrieve every registered output handler in no particular order.
* @return Every registered output handler.
*/
@Nonnull
public Collection<PacketOutputHandler> getOutputHandlers() {
if (outputHandlers != null) {
return outputHandlers;
} else {
return Collections.emptyList();
}
}
/**
* Add a listener that is invoked after a packet has been successfully sent to the client, or received
* by the server.
* <p>
* Received packets are not guarenteed to have been fully processed, but packets passed
* to {@link ProtocolManager#recieveClientPacket(Player, PacketContainer)} will be processed after the
* current packet event.
* <p>
* Note that post listeners will be executed asynchronously off the main thread. They are not executed
* in any defined order.
* @param listener - the listener that will be invoked.
* @return TRUE if it was added.
*/
public boolean addPostListener(PacketPostListener listener) {
if (postListeners == null)
postListeners = Lists.newArrayList();
return postListeners.add(listener);
}
/**
* Remove the first instance of the given listener.
* @param listener - listener to remove.
* @return TRUE if it was removed, FALSE otherwise.
*/
public boolean removePostListener(PacketPostListener listener) {
if (postListeners != null) {
return postListeners.remove(listener);
}
return false;
}
/**
* Retrieve an immutable view of all the listeners that will be invoked once the packet has been sent or received.
* @return Every post packet listener. Never NULL.
*/
public List<PacketPostListener> getPostListeners() {
return postListeners != null ? Collections.unmodifiableList(postListeners) : Collections.<PacketPostListener>emptyList();
}
/**
* Retrieve a list of packets that will be schedule (in-order) when the current packet has been successfully transmitted.
* <p>
* This list is modifiable.
* @return List of packets that will be scheduled.
*/
public List<ScheduledPacket> getScheduledPackets() {
if (scheduledPackets == null)
scheduledPackets = Lists.newArrayList();
return scheduledPackets;
}
/**
* Ensure that the packet event is server side.
*/
private void checkServerSide() {
if (side.isForClient()) {
throw new IllegalStateException("Must be a server side packet.");
}
}
/**
* Return a byte buffer without the header in the current packet.
* <p>
* It's safe to modify the position of the buffer.
* @param buffer - a read-only byte source.
* @return A byte buffer without the header in the current packet.
* @throws IOException If integer reading fails
*/
protected ByteBuffer skipHeader(ByteBuffer buffer) throws IOException {
skipHeader(new DataInputStream(new ByteBufferInputStream(buffer)));
return buffer;
}
/**
* Return an input stream without the header in the current packet.
* <p>
* It's safe to modify the input stream.
* @param input - input stream
* @return An input stream without the header
* @throws IOException If integer reading fails
*/
protected abstract DataInputStream skipHeader(DataInputStream input) throws IOException;
/**
* Return the byte buffer prepended with the packet header.
* @param buffer - the read-only byte buffer.
* @param type - the current packet.
* @return The byte buffer.
*/
protected abstract ByteBuffer addHeader(ByteBuffer buffer, PacketType type);
/**
* Return the input stream prepended with the packet header.
* @param input - the input stream.
* @param type - the current packet.
* @return The byte buffer.
*/
protected abstract DataInputStream addHeader(DataInputStream input, PacketType type);
/**
* Determine if the given marker has any output handlers.
* @param marker - the marker to check.
* @return TRUE if it does, FALSE otherwise.
*/
public static boolean hasOutputHandlers(NetworkMarker marker) {
return marker != null && !marker.getOutputHandlers().isEmpty();
}
/**
* Determine if the given marker has any post listeners.
*
* @param marker - the marker to check.
* @return TRUE if it does, FALSE otherwise.
*/
public static boolean hasPostListeners(NetworkMarker marker) {
return marker != null && !marker.getPostListeners().isEmpty();
}
/**
* Retrieve the byte buffer stored in the given marker.
* @param marker - the marker.
* @return The byte buffer, or NULL if not found.
*/
public static byte[] getByteBuffer(NetworkMarker marker) {
if (marker != null) {
ByteBuffer buffer = marker.getInputBuffer();
if (buffer != null) {
byte[] data = new byte[buffer.remaining()];
buffer.get(data, 0, data.length);
return data;
}
}
return null;
}
/**
* Retrieve the network marker of a particular event without creating it.
* <p>
* This is an internal method that should not be used by API users.
*
* @param event - the event.
* @return The network marker.
*/
public static NetworkMarker getNetworkMarker(PacketEvent event) {
return event.networkMarker;
}
/**
* Retrieve the scheduled packets of a particular network marker without constructing the list.
* <p>
* This is an internal method that should not be used by API users.
*
* @param marker - the marker.
* @return The list, or NULL if not found or initialized.
*/
public static List<ScheduledPacket> readScheduledPackets(NetworkMarker marker) {
public static Deque<ScheduledPacket> readScheduledPackets(NetworkMarker marker) {
return marker.scheduledPackets;
}
/**
* Retrieve whether or not this marker belongs to a client or a server side packet.
*
* @return The side the parent packet belongs to.
*/
public ConnectionSide getSide() {
return this.side;
}
public PacketType getType() {
return this.type;
}
/**
* Add a listener that is invoked after a packet has been successfully sent to the client, or received by the server.
* <p>
* Received packets are not guarenteed to have been fully processed, but packets passed to {@link
* ProtocolManager#receiveClientPacket(Player, PacketContainer)} will be processed after the current packet event.
* <p>
* Note that post listeners will be executed asynchronously off the main thread. They are not executed in any defined
* order.
*
* @param listener - the listener that will be invoked.
* @return TRUE if it was added.
*/
public boolean addPostListener(PacketPostListener listener) {
if (this.postListeners == null) {
this.postListeners = new HashSet<>();
}
return this.postListeners.add(listener);
}
/**
* Remove the first instance of the given listener.
*
* @param listener - listener to remove.
* @return TRUE if it was removed, FALSE otherwise.
*/
public boolean removePostListener(PacketPostListener listener) {
if (this.postListeners != null) {
return this.postListeners.remove(listener);
}
return false;
}
/**
* Retrieve an immutable view of all the listeners that will be invoked once the packet has been sent or received.
*
* @return Every post packet listener. Never NULL.
*/
public Set<PacketPostListener> getPostListeners() {
return this.postListeners != null ? this.postListeners : Collections.emptySet();
}
/**
* Retrieve a modifiable queue of packets that will be schedule (in-order) when the current packet has been
* successfully transmitted.
*
* @return the queue of packets to schedule after this packet, in order.
*/
public Deque<ScheduledPacket> getScheduledPackets() {
if (this.scheduledPackets == null) {
this.scheduledPackets = new LinkedList<>();
}
return this.scheduledPackets;
}
/**
* Ensure that the packet event is server side.
*/
private void checkServerSide() {
if (this.side.isForClient()) {
throw new IllegalStateException("Must be a server side packet.");
}
}
}
@@ -17,337 +17,167 @@
package com.comphenix.protocol.events;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bukkit.plugin.Plugin;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.bukkit.plugin.Plugin;
/**
* Represents a packet listener with useful constructors.
* <p>
* Remember to override onPacketReceiving() and onPacketSending(), depending on the ConnectionSide.
*
* @author Kristian
*/
public abstract class PacketAdapter implements PacketListener {
protected Plugin plugin;
protected ConnectionSide connectionSide;
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
/**
* Initialize a packet adapter using a collection of parameters. Use {@link #params()} to get an instance to this builder.
* Initialize a packet adapter using a collection of parameters. Use {@link #params()} to get an instance to this
* builder.
*
* @param params - the parameters.
*/
public PacketAdapter(@Nonnull AdapterParameteters params) {
this(
checkValidity(params).plugin, params.connectionSide, params.listenerPriority,
params.gamePhase, params.options, params.packets
checkValidity(params).plugin, params.connectionSide, params.listenerPriority,
params.gamePhase, params.options, params.packets
);
}
/**
* Initialize a packet listener with the given parameters.
*
* @param plugin - the plugin.
* @param types - the packet types.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, PacketType... types) {
this(plugin, ListenerPriority.NORMAL, types);
}
/**
* Initialize a packet listener with the given parameters.
*
* @param plugin - the plugin.
* @param types - the packet types.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, Iterable<? extends PacketType> types) {
this(params(plugin, Iterables.toArray(types, PacketType.class)));
}
/**
* Initialize a packet listener with the given parameters.
* @param plugin - the plugin.
*
* @param plugin - the plugin.
* @param listenerPriority - the priority.
* @param types - the packet types.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types) {
this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority));
}
/**
* Initialize a packet listener with the given parameters.
* @param plugin - the plugin.
*
* @param plugin - the plugin.
* @param listenerPriority - the priority.
* @param types - the packet types.
* @param options - the options.
* @param types - the packet types.
* @param options - the options.
*/
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types, ListenerOptions... options) {
this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options));
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types,
ListenerOptions... options) {
this(
params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options));
}
/**
* Initialize a packet listener with the given parameters.
* @param plugin - the plugin.
*
* @param plugin - the plugin.
* @param listenerPriority - the priority.
* @param types - the packet types.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, PacketType... types) {
this(params(plugin, types).listenerPriority(listenerPriority));
}
/**
* Initialize a packet listener with default priority.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Set<Integer> packets) {
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets.toArray(new Integer[0]));
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Set<Integer> packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets.toArray(new Integer[0]));
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Set<Integer> packets) {
this(plugin, connectionSide, listenerPriority, gamePhase, packets.toArray(new Integer[0]));
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) {
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param options - which listener options to use.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerOptions[] options, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, GamePhase.PLAYING, options, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Integer... packets) {
this(plugin, connectionSide, listenerPriority, gamePhase, new ListenerOptions[0], packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Listener options must be specified in order for {@link NetworkMarker#getInputBuffer()} to function correctly.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param options - which listener options to use.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(
Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
GamePhase gamePhase, ListenerOptions[] options, Integer... packets) {
this(plugin, connectionSide, listenerPriority, gamePhase, options,
PacketRegistry.toPacketTypes(Sets.newHashSet(packets), connectionSide.getSender()).toArray(new PacketType[0])
);
}
// For internal use only
private PacketAdapter(
Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
GamePhase gamePhase, ListenerOptions[] options, PacketType... packets) {
if (plugin == null)
if (plugin == null) {
throw new IllegalArgumentException("plugin cannot be null");
if (connectionSide == null)
throw new IllegalArgumentException("connectionSide cannot be null");
if (listenerPriority == null)
throw new IllegalArgumentException("listenerPriority cannot be null");
if (gamePhase == null)
throw new IllegalArgumentException("gamePhase cannot be NULL");
if (packets == null)
throw new IllegalArgumentException("packets cannot be null");
if (options == null)
throw new IllegalArgumentException("options cannot be null");
ListenerOptions[] serverOptions = options;
ListenerOptions[] clientOptions = options;
// Special case that allows us to specify optionIntercept().
if (connectionSide == ConnectionSide.BOTH) {
serverOptions = except(serverOptions, new ListenerOptions[0],
ListenerOptions.INTERCEPT_INPUT_BUFFER);
}
if (connectionSide == null) {
throw new IllegalArgumentException("connectionSide cannot be null");
}
if (listenerPriority == null) {
throw new IllegalArgumentException("listenerPriority cannot be null");
}
if (gamePhase == null) {
throw new IllegalArgumentException("gamePhase cannot be NULL");
}
if (packets == null) {
throw new IllegalArgumentException("packets cannot be null");
}
if (options == null) {
throw new IllegalArgumentException("options cannot be null");
}
// Add whitelists
if (connectionSide.isForServer())
sendingWhitelist = ListeningWhitelist.newBuilder().
priority(listenerPriority).
types(packets).
gamePhase(gamePhase).
options(serverOptions).
build();
if (connectionSide.isForClient())
receivingWhitelist = ListeningWhitelist.newBuilder().
priority(listenerPriority).
types(packets).
gamePhase(gamePhase).
options(clientOptions).
build();
if (connectionSide.isForServer()) {
sendingWhitelist = ListeningWhitelist.newBuilder()
.priority(listenerPriority)
.types(packets)
.gamePhase(gamePhase)
.options(options)
.build();
}
if (connectionSide.isForClient()) {
receivingWhitelist = ListeningWhitelist.newBuilder()
.priority(listenerPriority)
.types(packets)
.gamePhase(gamePhase)
.options(options)
.build();
}
this.plugin = plugin;
this.connectionSide = connectionSide;
}
// Remove a given element from an array
private static <T> T[] except(T[] values, T[] buffer, T except) {
List<T> result = Lists.newArrayList(values);
result.remove(except);
return result.toArray(buffer);
}
@Override
public void onPacketReceiving(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
}
@Override
public void onPacketSending(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
/**
* Retrieves the name of the plugin that has been associated with the listener.
*
* @param listener - the listener.
* @return Name of the associated plugin.
*/
public static String getPluginName(PacketListener listener) {
return getPluginName(listener.getPlugin());
}
/**
* Retrieves the name of the given plugin.
*
* @param plugin - the plugin.
* @return Name of the given plugin.
*/
public static String getPluginName(Plugin plugin) {
if (plugin == null)
if (plugin == null) {
return "UNKNOWN";
}
try {
return plugin.getName();
@@ -355,7 +185,77 @@ public abstract class PacketAdapter implements PacketListener {
return plugin.toString();
}
}
/**
* Construct a helper object for passing parameters to the packet adapter.
* <p>
* This is often simpler and better than passing them directly to each constructor.
*
* @return Helper object.
*/
public static AdapterParameteters params() {
return new AdapterParameteters();
}
/**
* Construct a helper object for passing parameters to the packet adapter.
* <p>
* This is often simpler and better than passing them directly to each constructor.
*
* @param plugin - the plugin that spawned this listener.
* @param packets - the packet types the listener is looking for.
* @return Helper object.
*/
public static AdapterParameteters params(Plugin plugin, PacketType... packets) {
return new AdapterParameteters().plugin(plugin).types(packets);
}
/**
* Determine if the required parameters are set.
*/
private static AdapterParameteters checkValidity(AdapterParameteters params) {
if (params == null) {
throw new IllegalArgumentException("params cannot be NULL.");
}
if (params.plugin == null) {
throw new IllegalStateException("Plugin was never set in the parameters.");
}
if (params.connectionSide == null) {
throw new IllegalStateException("Connection side was never set in the parameters.");
}
if (params.packets == null) {
throw new IllegalStateException("Packet IDs was never set in the parameters.");
}
return params;
}
@Override
public void onPacketReceiving(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
}
@Override
public void onPacketSending(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
@Override
public String toString() {
// This is used by the error reporter
@@ -364,61 +264,28 @@ public abstract class PacketAdapter implements PacketListener {
sendingWhitelist,
receivingWhitelist);
}
/**
* Construct a helper object for passing parameters to the packet adapter.
* <p>
* This is often simpler and better than passing them directly to each constructor.
* @return Helper object.
*/
public static AdapterParameteters params() {
return new AdapterParameteters();
}
/**
* Construct a helper object for passing parameters to the packet adapter.
* <p>
* This is often simpler and better than passing them directly to each constructor.
* Deprecated: Use {@link #params(Plugin, PacketType...)} instead.
* @param plugin - the plugin that spawned this listener.
* @param packets - the packet IDs the listener is looking for.
* @return Helper object.
*/
@Deprecated
public static AdapterParameteters params(Plugin plugin, Integer... packets) {
return new AdapterParameteters().plugin(plugin).packets(packets);
}
/**
* Construct a helper object for passing parameters to the packet adapter.
* <p>
* This is often simpler and better than passing them directly to each constructor.
* @param plugin - the plugin that spawned this listener.
* @param packets - the packet types the listener is looking for.
* @return Helper object.
*/
public static AdapterParameteters params(Plugin plugin, PacketType... packets) {
return new AdapterParameteters().plugin(plugin).types(packets);
}
/**
* Represents a builder for passing parameters to the packet adapter constructor.
* <p>
* Note: Never make spelling mistakes in a public API!
*
* @author Kristian
*/
public static class AdapterParameteters {
private Plugin plugin;
private ConnectionSide connectionSide;
private PacketType[] packets;
// Parameters with default values
private GamePhase gamePhase = GamePhase.PLAYING;
private ListenerOptions[] options = new ListenerOptions[0];
private ListenerPriority listenerPriority = ListenerPriority.NORMAL;
/**
* Set the plugin that spawned this listener. This parameter is required.
*
* @param plugin - the plugin.
* @return This builder, for chaining.
*/
@@ -426,9 +293,10 @@ public abstract class PacketAdapter implements PacketListener {
this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL.");
return this;
}
/**
* Set the packet types this listener is looking for. This parameter is required.
*
* @param connectionSide - the new packet type.
* @return This builder, for chaining.
*/
@@ -436,27 +304,30 @@ public abstract class PacketAdapter implements PacketListener {
this.connectionSide = Preconditions.checkNotNull(connectionSide, "connectionside cannot be NULL.");
return this;
}
/**
* Set this adapter to also look for client-side packets.
*
* @return This builder, for chaining.
*/
public AdapterParameteters clientSide() {
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.CLIENT_SIDE));
}
/**
* Set this adapter to also look for server-side packets.
*
* @return This builder, for chaining.
*/
public AdapterParameteters serverSide() {
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.SERVER_SIDE));
}
/**
* Set the the event priority, where the execution is in ascending order from lowest to highest.
* <p>
* Default is {@link ListenerPriority#NORMAL}.
*
* @param listenerPriority - the new event priority.
* @return This builder, for chaining.
*/
@@ -464,11 +335,13 @@ public abstract class PacketAdapter implements PacketListener {
this.listenerPriority = Preconditions.checkNotNull(listenerPriority, "listener priority cannot be NULL.");
return this;
}
/**
* Set which game phase this listener is active under. This is a hint for ProtocolLib to start intercepting login packets.
* Set which game phase this listener is active under. This is a hint for ProtocolLib to start intercepting login
* packets.
* <p>
* Default is {@link GamePhase#PLAYING}, which will not intercept login packets.
*
* @param gamePhase - the new game phase.
* @return This builder, for chaining.
*/
@@ -476,19 +349,22 @@ public abstract class PacketAdapter implements PacketListener {
this.gamePhase = Preconditions.checkNotNull(gamePhase, "gamePhase cannot be NULL.");
return this;
}
/**
* Set the game phase to {@link GamePhase#LOGIN}, allowing ProtocolLib to intercept login packets.
*
* @return This builder, for chaining.
*/
public AdapterParameteters loginPhase() {
return gamePhase(GamePhase.LOGIN);
}
/**
* Set listener options that decide whether or not to intercept the raw packet data as read from the network stream.
* Set listener options that decide whether or not to intercept the raw packet data as read from the network
* stream.
* <p>
* The default is to disable this raw packet interception.
*
* @param options - every option to use.
* @return This builder, for chaining.
*/
@@ -498,9 +374,11 @@ public abstract class PacketAdapter implements PacketListener {
}
/**
* Set listener options that decide whether or not to intercept the raw packet data as read from the network stream.
* Set listener options that decide whether or not to intercept the raw packet data as read from the network
* stream.
* <p>
* The default is to disable this raw packet interception.
*
* @param options - every option to use.
* @return This builder, for chaining.
*/
@@ -509,9 +387,10 @@ public abstract class PacketAdapter implements PacketListener {
this.options = options.toArray(new ListenerOptions[0]);
return this;
}
/**
* Add a given option to the current builder.
*
* @param option - the option to add.
* @return This builder, for chaining.
*/
@@ -519,77 +398,38 @@ public abstract class PacketAdapter implements PacketListener {
if (options == null) {
return options(option);
} else {
Set<ListenerOptions> current = Sets.newHashSet(options);
final Set<ListenerOptions> current = Sets.newHashSet(options);
current.add(option);
return options(current);
}
}
/**
* Set the listener option to {@link ListenerOptions#INTERCEPT_INPUT_BUFFER}, causing ProtocolLib to read the raw packet data from the network stream.
* @return This builder, for chaining.
*/
public AdapterParameteters optionIntercept() {
return addOption(ListenerOptions.INTERCEPT_INPUT_BUFFER);
}
/**
* Set the listener option to {@link ListenerOptions#DISABLE_GAMEPHASE_DETECTION}, causing ProtocolLib to ignore automatic game phase detection.
* <p>
* This is no longer relevant in 1.7.2.
* @return This builder, for chaining.
*/
public AdapterParameteters optionManualGamePhase() {
return addOption(ListenerOptions.DISABLE_GAMEPHASE_DETECTION);
}
/**
* Set the listener option to {@link ListenerOptions#ASYNC}, indicating that our listener is thread safe.
* Set the listener option to {@link ListenerOptions#ASYNC}, indicating that our outbound listener is thread safe.
* <p>
* This allows ProtocolLib to perform certain optimizations.
*
* @return This builder, for chaining.
*/
public AdapterParameteters optionAsync() {
return addOption(ListenerOptions.ASYNC);
}
/**
* Set the packet IDs of the packets the listener is looking for.
* <p>
* This parameter is required.
* <p>
* Deprecated: Use {@link #types(PacketType...)} instead.
* @param packets - the packet IDs to look for.
* Set the listener option to {@link ListenerOptions#SYNC}, indicating that our inbound listener must be executed
* on the main server thread.
*
* @return This builder, for chaining.
*/
@Deprecated
public AdapterParameteters packets(@Nonnull Integer... packets) {
Preconditions.checkNotNull(packets, "packets cannot be NULL");
PacketType[] types = new PacketType[packets.length];
for (int i = 0; i < types.length; i++) {
types[i] = PacketType.findLegacy(packets[i]);
}
this.packets = types;
return this;
public AdapterParameteters optionSync() {
return addOption(ListenerOptions.SYNC);
}
/**
* Set the packet IDs of the packets the listener is looking for.
* <p>
* This parameter is required.
* @param packets - a set of the packet IDs to look for.
* @return This builder, for chaining.
*/
@Deprecated
public AdapterParameteters packets(@Nonnull Set<Integer> packets) {
return packets(packets.toArray(new Integer[0]));
}
/**
* Set the packet types the listener is looking for.
* <p>
* This parameter is required.
*
* @param packets - the packet types to look for.
* @return This builder, for chaining.
*/
@@ -601,16 +441,18 @@ public abstract class PacketAdapter implements PacketListener {
}
}
this.packets = Preconditions.checkNotNull(packets, "packets cannot be NULL");
if (packets.length == 0)
if (packets.length == 0) {
throw new IllegalArgumentException("Passed an empty packet type array.");
}
return this;
}
/**
* Set the packet types the listener is looking for.
* <p>
* This parameter is required.
*
* @param packets - a set of packet types to look for.
* @return This builder, for chaining.
*/
@@ -618,19 +460,4 @@ public abstract class PacketAdapter implements PacketListener {
return types(packets.toArray(new PacketType[0]));
}
}
/**
* Determine if the required parameters are set.
*/
private static AdapterParameteters checkValidity(AdapterParameteters params) {
if (params == null)
throw new IllegalArgumentException("params cannot be NULL.");
if (params.plugin == null)
throw new IllegalStateException("Plugin was never set in the parameters.");
if (params.connectionSide == null)
throw new IllegalStateException("Connection side was never set in the parameters.");
if (params.packets == null)
throw new IllegalStateException("Packet IDs was never set in the parameters.");
return params;
}
}
File diff suppressed because it is too large. Load diff
@@ -23,58 +23,56 @@ import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import java.util.EventObject;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.PluginContext;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.server.TemporaryPlayer;
import com.comphenix.protocol.injector.temporary.TemporaryPlayer;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
/**
* Represents a packet sending or receiving event. Changes to the packet will be
* reflected in the final version to be sent or received. It is also possible to cancel an event.
* Represents a packet sending or receiving event. Changes to the packet will be reflected in the final version to be
* sent or received. It is also possible to cancel an event.
*
* @author Kristian
*/
public class PacketEvent extends EventObject implements Cancellable {
public static final ReportType REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING = new ReportType(
"Plugin %s changed packet type from %s to %s in packet listener. This is confusing for other plugins! (Not an error, though!)");
private static final SetMultimap<PacketType, PacketType> CHANGE_WARNINGS =
Multimaps.synchronizedSetMultimap(HashMultimap.create());
/**
* Automatically generated by Eclipse.
*/
private static final long serialVersionUID = -5360289379097430620L;
// Network input and output handlers
NetworkMarker networkMarker;
private transient WeakReference<Player> playerReference;
private PacketContainer packet;
private boolean serverPacket;
private boolean cancel;
private AsyncMarker asyncMarker;
private boolean asynchronous;
// Network input and output handlers
NetworkMarker networkMarker;
// Whether or not a packet event is read only
private boolean readOnly;
private boolean filtered;
/**
* Use the static constructors to create instances of this event.
*
* @param source - the event source.
*/
public PacketEvent(Object source) {
@@ -85,8 +83,9 @@ public class PacketEvent extends EventObject implements Cancellable {
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
this(source, packet, null, player, serverPacket, true);
}
private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket, boolean filtered) {
private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket,
boolean filtered) {
super(source);
this.packet = packet;
this.playerReference = new WeakReference<>(player);
@@ -94,7 +93,7 @@ public class PacketEvent extends EventObject implements Cancellable {
this.serverPacket = serverPacket;
this.filtered = filtered;
}
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
super(origial.source);
this.packet = origial.packet;
@@ -109,6 +108,7 @@ public class PacketEvent extends EventObject implements Cancellable {
/**
* Creates an event representing a client packet transmission.
*
* @param source - the event source.
* @param packet - the packet.
* @param client - the client that sent the packet.
@@ -117,9 +117,10 @@ public class PacketEvent extends EventObject implements Cancellable {
public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
return new PacketEvent(source, packet, client, false);
}
/**
* Creates an event representing a client packet transmission.
*
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
@@ -129,85 +130,94 @@ public class PacketEvent extends EventObject implements Cancellable {
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) {
return new PacketEvent(source, packet, marker, client, false, true);
}
/**
* Creates an event representing a client packet transmission.
* <p>
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param client - the client that sent the packet.
*
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param client - the client that sent the packet.
* @param filtered - whether or not this packet event is processed by every packet listener.
* @return The event.
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client, boolean filtered) {
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client,
boolean filtered) {
return new PacketEvent(source, packet, marker, client, false, filtered);
}
/**
* Creates an event representing a server packet transmission.
* @param source - the event source.
* @param packet - the packet.
*
* @param source - the event source.
* @param packet - the packet.
* @param recipient - the client that will receieve the packet.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
return new PacketEvent(source, packet, recipient, true);
}
/**
* Creates an event representing a server packet transmission.
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
*
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param recipient - the client that will receieve the packet.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) {
return new PacketEvent(source, packet, marker, recipient, true, true);
}
/**
* Creates an event representing a server packet transmission.
* <p>
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
*
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param recipient - the client that will receieve the packet.
* @param filtered - whether or not this packet event is processed by every packet listener.
* @param filtered - whether or not this packet event is processed by every packet listener.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient, boolean filtered) {
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
boolean filtered) {
return new PacketEvent(source, packet, marker, recipient, true, filtered);
}
/**
* Create an asynchronous packet event from a synchronous event and a async marker.
* @param event - the original synchronous event.
*
* @param event - the original synchronous event.
* @param marker - the asynchronous marker.
* @return The new packet event.
*/
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
return new PacketEvent(event, marker);
}
/**
* Determine if we are executing the packet event in an asynchronous thread.
* <p>
* If so, you must synchronize all calls to the Bukkit API.
* <p>
* Generally, most server packets are executed on the main thread, whereas client packets
* are all executed asynchronously.
* Generally, most server packets are executed on the main thread, whereas client packets are all executed
* asynchronously.
*
* @return TRUE if we are, FALSE otherwise.
*/
public boolean isAsync() {
return !Bukkit.isPrimaryThread();
}
/**
* Retrieves the packet that will be sent to the player.
*
* @return Packet to send to the player.
*/
public PacketContainer getPacket() {
@@ -216,14 +226,17 @@ public class PacketEvent extends EventObject implements Cancellable {
/**
* Replace the packet that will be sent to the player.
*
* @param packet - the packet that will be sent instead.
*/
public void setPacket(PacketContainer packet) {
if (readOnly)
if (readOnly) {
throw new IllegalStateException("The packet event is read-only.");
if (packet == null)
}
if (packet == null) {
throw new IllegalArgumentException("Cannot set packet to NULL. Use setCancelled() instead.");
}
// Change warnings
final PacketType oldType = this.packet.getType();
final PacketType newType = packet.getType();
@@ -232,53 +245,77 @@ public class PacketEvent extends EventObject implements Cancellable {
if (CHANGE_WARNINGS.put(oldType, newType)) {
ProtocolLibrary.getErrorReporter().reportWarning(this,
Report.newBuilder(REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING).
messageParam(PluginContext.getPluginCaller(new Exception()), oldType, newType).
build());
messageParam(PluginContext.getPluginCaller(new Exception()), oldType, newType).
build());
}
}
this.packet = packet;
}
/**
* Retrieves the packet ID.
* <p>
* Deprecated: Use {@link #getPacketType()} instead.
*
* @return The current packet ID.
*/
@Deprecated
public int getPacketID() {
return packet.getID();
return packet.getId();
}
/**
* Retrieve the packet type.
*
* @return The type.
*/
public PacketType getPacketType() {
return packet.getType();
}
/**
* Retrieves whether or not the packet should be cancelled.
*
* @return TRUE if it should be cancelled, FALSE otherwise.
*/
@Override
public boolean isCancelled() {
return cancel;
}
/**
* Sets whether or not the packet should be cancelled. Uncancelling is possible.
* <p>
* <b>Warning</b>: A cancelled packet should never be re-transmitted. Use the asynchronous
* packet manager if you need to perform extensive processing. It should also be used if you need to synchronize with
* the main thread.
* <p>
* This ensures that other plugins can work with the same packet.
* <p>
* An asynchronous listener can also delay a packet indefinitely without having to block its thread.
*
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
*/
@Override
public void setCancelled(boolean cancel) {
if (readOnly) {
throw new IllegalStateException("The packet event is read-only.");
}
this.cancel = cancel;
}
/**
* Retrieve the object responsible for managing the serialized input and output of a packet.
* <p>
* Note that the serialized input data is only available for client-side packets, and the output handlers
* can only be applied to server-side packets.
* Note that the serialized input data is only available for client-side packets, and the output handlers can only be
* applied to server-side packets.
*
* @return The network manager.
*/
public NetworkMarker getNetworkMarker() {
if (networkMarker == null) {
if (isServerPacket()) {
networkMarker = new NetworkMarker.EmptyBufferMarker(
serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE);
networkMarker = new NetworkMarker(serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE, this.getPacketType());
} else {
throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener.");
}
@@ -290,31 +327,12 @@ public class PacketEvent extends EventObject implements Cancellable {
* Update the network manager.
* <p>
* This method is internal - do not call.
*
* @param networkMarker - the new network manager.
*/
public void setNetworkMarker(NetworkMarker networkMarker) {
this.networkMarker = Preconditions.checkNotNull(networkMarker, "marker cannot be NULL");
}
/**
* Sets whether or not the packet should be cancelled. Uncancelling is possible.
* <p>
* <b>Warning</b>: A cancelled packet should never be re-transmitted. Use the asynchronous
* packet manager if you need to perform extensive processing. It should also be used
* if you need to synchronize with the main thread.
* <p>
* This ensures that other plugins can work with the same packet.
* <p>
* An asynchronous listener can also delay a packet indefinitely without having to block its thread.
*
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
*/
@Override
public void setCancelled(boolean cancel) {
if (readOnly)
throw new IllegalStateException("The packet event is read-only.");
this.cancel = cancel;
}
private WeakReference<Player> getPlayerReference() {
Player player = playerReference.get();
@@ -332,6 +350,7 @@ public class PacketEvent extends EventObject implements Cancellable {
/**
* Retrieves the player that has sent the packet or is receiving it.
*
* @return The player associated with this event.
*/
public Player getPlayer() {
@@ -350,7 +369,7 @@ public class PacketEvent extends EventObject implements Cancellable {
* <li>kickPlayer</li>
* <li>isOnline</li>
* </ul>
*
* <p>
* Anything else will throw an UnsupportedOperationException. Use this check before calling other methods when
* dealing with packets early in the login sequence or if you get the aforementioned exception.
*
@@ -359,27 +378,29 @@ public class PacketEvent extends EventObject implements Cancellable {
public boolean isPlayerTemporary() {
return getPlayer() instanceof TemporaryPlayer;
}
/**
* Determine if this packet is filtered by every packet listener.
* <p>
* If not, it will only be intercepted by monitor packets.
*
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isFiltered() {
return filtered;
}
/**
* Whether or not this packet was created by the server.
* <p>
* Most listeners can deduce this by noting which listener method was invoked.
*
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
*/
public boolean isServerPacket() {
return serverPacket;
}
/**
* Retrieve the asynchronous marker.
* <p>
@@ -387,54 +408,62 @@ public class PacketEvent extends EventObject implements Cancellable {
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
* <p>
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
*
* @return The current asynchronous marker, or NULL.
*/
public AsyncMarker getAsyncMarker() {
return asyncMarker;
}
/**
* Set the asynchronous marker.
* <p>
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled
* to be processed asynchronously with the given settings.
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled to be
* processed asynchronously with the given settings.
* <p>
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
*
* @param asyncMarker - the new asynchronous marker, or NULL.
* @throws IllegalStateException If the current event is asynchronous.
*/
public void setAsyncMarker(AsyncMarker asyncMarker) {
if (isAsynchronous())
if (isAsynchronous()) {
throw new IllegalStateException("The marker is immutable for asynchronous events");
if (readOnly)
}
if (readOnly) {
throw new IllegalStateException("The packet event is read-only.");
}
this.asyncMarker = asyncMarker;
}
/**
* Determine if the current packet event is read only.
* <p>
* This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However,
* it is still possible to modify the packet itself, as it would require too many resources to verify its integrity.
* This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However, it is
* still possible to modify the packet itself, as it would require too many resources to verify its integrity.
* <p>
* Thus, the packet is considered immutable if the packet event is read only.
*
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Set the read-only state of this packet event.
* <p>
* This will be reset for every packet listener.
*
* @param readOnly - TRUE if it is read-only, FALSE otherwise.
*/
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/**
* Determine if the packet event has been executed asynchronously or not.
*
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
*/
public boolean isAsynchronous() {
@@ -445,14 +474,16 @@ public class PacketEvent extends EventObject implements Cancellable {
* Schedule a packet for sending or receiving after the current packet event is successful.
* <p>
* The packet will be added to {@link NetworkMarker#getScheduledPackets()}.
*
* @param scheduled - the packet to transmit or receive.
*/
public void schedule(ScheduledPacket scheduled) {
getNetworkMarker().getScheduledPackets().add(scheduled);
}
/**
* Unschedule a specific packet.
*
* @param scheduled - the scheduled packet.
* @return TRUE if it was unscheduled, FALSE otherwise.
*/
@@ -462,9 +493,9 @@ public class PacketEvent extends EventObject implements Cancellable {
}
return false;
}
private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization
// Default serialization
output.defaultWriteObject();
// Write the name of the player (or NULL if it's not set)
@@ -473,17 +504,17 @@ public class PacketEvent extends EventObject implements Cancellable {
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
// Default deserialization
input.defaultReadObject();
final SerializedOfflinePlayer serialized = (SerializedOfflinePlayer) input.readObject();
// Better than nothing
if (serialized != null) {
// Store it, to prevent weak reference from cleaning up the reference
Player offlinePlayer = serialized.getPlayer();
playerReference = new WeakReference<>(offlinePlayer);
}
if (serialized != null) {
// Store it, to prevent weak reference from cleaning up the reference
Player offlinePlayer = serialized.getPlayer();
playerReference = new WeakReference<>(offlinePlayer);
}
}
@Override
@@ -23,49 +23,53 @@ import org.bukkit.plugin.Plugin;
* Represents a listener that receives notifications when packets are sending or being received.
* <p>
* Use {@link PacketAdapter} for a simple wrapper around this interface.
*
* @author Kristian
*/
public interface PacketListener {
/**
* Invoked right before a packet is transmitted from the server to the client.
* <p>
* Note that the packet may be replaced, if needed.
* <p>
* This method is executed on the main thread in 1.6.4 and earlier, and thus the Bukkit API is safe to use.
* <p>
* In Minecraft 1.7.2 and later, this method MAY be executed asynchronously, but only if {@link ListenerOptions#ASYNC}
* have been specified in the listener. This is off by default.
* This method is executed on the main server thread by default. However, some spigot forks (like paper) schedule
* specific packets off the main thread. If the {@link ListenerOptions#ASYNC} option is not specified any invocation
* of this method will be on the main server thread.
*
* @param event - the packet that should be sent.
*/
public void onPacketSending(PacketEvent event);
void onPacketSending(PacketEvent event);
/**
* Invoked right before a received packet from a client is being processed.
* <p>
* <b>WARNING</b>: <br>
* This method will be called <i>asynchronously</i>! You should synchronize with the main
* thread using {@link org.bukkit.scheduler.BukkitScheduler#scheduleSyncDelayedTask(Plugin, Runnable, long) scheduleSyncDelayedTask}
* if you need to call the Bukkit API.
* This method will be called asynchronously (or on the netty event loop) by default. If the
* {@link ListenerOptions#SYNC} option is specified, the invocation of this method will be synced to the main server
* thread which might cause issues due to delayed packets.
*
* @param event - the packet that has been received.
*/
public void onPacketReceiving(PacketEvent event);
void onPacketReceiving(PacketEvent event);
/**
* Retrieve which packets sent by the server this listener will observe.
*
* @return List of server packets to observe, along with the priority.
*/
public ListeningWhitelist getSendingWhitelist();
ListeningWhitelist getSendingWhitelist();
/**
* Retrieve which packets sent by the client this listener will observe.
*
* @return List of server packets to observe, along with the priority.
*/
public ListeningWhitelist getReceivingWhitelist();
ListeningWhitelist getReceivingWhitelist();
/**
* Retrieve the plugin that created list packet listener.
*
* @return The plugin, or NULL if not available.
*/
public Plugin getPlugin();
Plugin getPlugin();
}
@@ -1,142 +1,143 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2017 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.events;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.commons.lang.Validate;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Stores and retrieves metadata for applicable packet objects.
* @author dmulloy2
*/
class PacketMetadata {
private static class MetaObject<T> {
private final String key;
private final T value;
private MetaObject(String key, T value) {
this.key = key;
this.value = value;
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o instanceof MetaObject) {
MetaObject that = (MetaObject) o;
return that.key.equals(this.key) &&
that.value.equals(this.value);
}
return false;
}
@Override
public String toString() {
return "MetaObject[" + key + "=" + value + "]";
}
}
// Packet meta cache
private static Cache<Object, List<MetaObject>> META_CACHE;
public static <T> Optional<T> get(Object packet, String key) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
return Optional.empty();
}
List<MetaObject> meta = META_CACHE.getIfPresent(packet);
if (meta == null) {
return Optional.empty();
}
for (MetaObject object : meta) {
if (object.key.equals(key)) {
return Optional.of((T) object.value);
}
}
return Optional.empty();
}
private static void createCache() {
META_CACHE = CacheBuilder
.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
}
public static <T> void set(Object packet, String key, T value) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
createCache();
}
List<MetaObject> packetMeta;
try {
packetMeta = META_CACHE.get(packet, ArrayList::new);
} catch (ExecutionException ex) {
// Not possible, but let's humor the array list constructor having an issue
packetMeta = new ArrayList<>();
}
packetMeta.removeIf(meta -> meta.key.equals(key));
packetMeta.add(new MetaObject<>(key, value));
META_CACHE.put(packet, packetMeta);
}
public static <T> Optional<T> remove(Object packet, String key) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
return Optional.empty();
}
List<MetaObject> packetMeta = META_CACHE.getIfPresent(packet);
if (packetMeta == null) {
return Optional.empty();
}
Optional<T> value = Optional.empty();
Iterator<MetaObject> iter = packetMeta.iterator();
while (iter.hasNext()) {
MetaObject meta = iter.next();
if (meta.key.equals(key)) {
value = Optional.of((T) meta.value);
iter.remove();
}
}
return value;
}
}
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2017 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.events;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.commons.lang.Validate;
/**
* Stores and retrieves metadata for applicable packet objects.
* @author dmulloy2
*/
class PacketMetadata {
private static class MetaObject<T> {
private final String key;
private final T value;
private MetaObject(String key, T value) {
this.key = key;
this.value = value;
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o instanceof MetaObject) {
MetaObject that = (MetaObject) o;
return that.key.equals(this.key) &&
that.value.equals(this.value);
}
return false;
}
@Override
public String toString() {
return "MetaObject[" + key + "=" + value + "]";
}
}
// Packet meta cache
private static Cache<Object, List<MetaObject>> META_CACHE;
public static <T> Optional<T> get(Object packet, String key) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
return Optional.empty();
}
List<MetaObject> meta = META_CACHE.getIfPresent(packet);
if (meta == null) {
return Optional.empty();
}
for (MetaObject object : meta) {
if (object.key.equals(key)) {
return Optional.of((T) object.value);
}
}
return Optional.empty();
}
private static void createCache() {
META_CACHE = CacheBuilder
.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
}
public static <T> void set(Object packet, String key, T value) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
createCache();
}
List<MetaObject> packetMeta;
try {
packetMeta = META_CACHE.get(packet, ArrayList::new);
} catch (ExecutionException ex) {
// Not possible, but let's humor the array list constructor having an issue
packetMeta = new ArrayList<>();
}
packetMeta.removeIf(meta -> meta.key.equals(key));
packetMeta.add(new MetaObject<>(key, value));
META_CACHE.put(packet, packetMeta);
}
public static <T> Optional<T> remove(Object packet, String key) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
return Optional.empty();
}
List<MetaObject> packetMeta = META_CACHE.getIfPresent(packet);
if (packetMeta == null) {
return Optional.empty();
}
Optional<T> value = Optional.empty();
Iterator<MetaObject> iter = packetMeta.iterator();
while (iter.hasNext()) {
MetaObject meta = iter.next();
if (meta.key.equals(key)) {
value = Optional.of((T) meta.value);
iter.remove();
}
}
return value;
}
}
@@ -1,9 +1,9 @@
package com.comphenix.protocol.events;
import org.bukkit.plugin.Plugin;
import com.google.common.base.Preconditions;
import org.bukkit.plugin.Plugin;
/**
* Represents an adapter version of a post listener.
* @author Kristian
@@ -1,14 +1,12 @@
package com.comphenix.protocol.events;
import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.ProtocolLibrary;
import com.google.common.base.Preconditions;
import org.bukkit.entity.Player;
/**
* Represents a packet that is scheduled for transmission at a later stage.
* @author Kristian
@@ -121,17 +119,11 @@ public class ScheduledPacket {
*/
public void schedule(PacketStream stream) {
Preconditions.checkNotNull(stream, "stream cannot be NULL");
try {
if (getSender() == Sender.CLIENT) {
stream.recieveClientPacket(getTarget(), getPacket(), isFiltered());
} else {
stream.sendServerPacket(getTarget(), getPacket(), isFiltered());
}
} catch (InvocationTargetException e) {
throw new RuntimeException("Cannot send packet " + this + " to " + stream);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot send packet " + this + " to " + stream);
if (getSender() == Sender.CLIENT) {
stream.receiveClientPacket(getTarget(), getPacket(), isFiltered());
} else {
stream.sendServerPacket(getTarget(), getPacket(), isFiltered());
}
}
@@ -21,22 +21,36 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
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 com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.*;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.profile.PlayerProfile;
import com.comphenix.protocol.utility.EnhancerFactory;
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
/**
* Represents a player object that can be serialized by Java.
@@ -62,9 +76,8 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
private boolean playedBefore;
private boolean online;
private boolean whitelisted;
// Proxy helper
private static Map<String, Method> lookup = new ConcurrentHashMap<String, Method>();
private static final Constructor<?> proxyPlayerConstructor = setupProxyPlayerConstructor();
/**
* Constructor used by serialization.
@@ -109,6 +122,55 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return bedSpawnLocation;
}
// TODO do we need to implement this?
public void incrementStatistic(Statistic statistic) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic) throws IllegalArgumentException { }
public void incrementStatistic(Statistic statistic, int i) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic, int i) throws IllegalArgumentException { }
public void setStatistic(Statistic statistic, int i) throws IllegalArgumentException { }
public int getStatistic(Statistic statistic) throws IllegalArgumentException {
return 0;
}
public void incrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { }
public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
return 0;
}
public void incrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { }
public void setStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { }
public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { }
public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
return 0;
}
public void incrementStatistic(Statistic statistic, EntityType entityType, int i) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic, EntityType entityType, int i) { }
public void setStatistic(Statistic statistic, EntityType entityType, int i) { }
@Override
public Location getLastDeathLocation() {
return null;
}
@Override
public long getFirstPlayed() {
return firstPlayed;
@@ -123,7 +185,12 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
public UUID getUniqueId() {
return uuid;
}
@Override
public PlayerProfile getPlayerProfile() {
return null;
}
@Override
public String getName() {
return name;
@@ -201,42 +268,93 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}
/**
* Retrieve a player object that implements OfflinePlayer by refering to this object.
* Retrieve a player object that implements OfflinePlayer by referring 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);
}
try {
return (Player) proxyPlayerConstructor.newInstance(this);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot instantiate object.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error in invocation.", e);
}
// 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();
private static Constructor<? extends Player> setupProxyPlayerConstructor()
{
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
final String[] methodNames = new String[offlinePlayerMethods.length];
for (int idx = 0; idx < offlinePlayerMethods.length; ++idx)
methodNames[idx] = offlinePlayerMethods[idx].getName();
final Map<Method, MethodAccessor> accessorCache = new ConcurrentHashMap<>();
final ElementMatcher.Junction<ByteCodeElement> forwardedMethods = ElementMatchers.namedOneOf(methodNames);
try {
final MethodDelegation forwarding = MethodDelegation.withDefaultConfiguration().to(new Object() {
@RuntimeType
public Object intercept(
@Origin Method calledMethod,
@AllArguments Object[] args,
@FieldValue("offlinePlayer") OfflinePlayer proxy
) {
MethodAccessor accessor = accessorCache.computeIfAbsent(calledMethod, method -> {
// special case - some methods (like getName) are defined in OfflinePlayer as well
// as the online Player class. This causes cast exceptions if we try to invoke the method on
// the online player with our proxy. Prevent that
if (OfflinePlayer.class.isAssignableFrom(method.getDeclaringClass())) {
return Accessors.getMethodAccessor(
OfflinePlayer.class,
method.getName(),
method.getParameterTypes());
} else {
return Accessors.getMethodAccessor(method);
}
});
return accessor.invoke(proxy, args);
}
});
final InvocationHandlerAdapter throwException = InvocationHandlerAdapter.of((obj, method, args) -> {
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players.");
});
return ByteBuddyFactory.getInstance()
.createSubclass(PlayerUnion.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name(SerializedOfflinePlayer.class.getPackage().getName() + ".PlayerInvocationHandler")
.defineField("offlinePlayer", OfflinePlayer.class, Visibility.PRIVATE)
.defineConstructor(Visibility.PUBLIC)
.withParameters(OfflinePlayer.class)
.intercept(MethodCall.invoke(Object.class.getDeclaredConstructor())
.andThen(FieldAccessor.ofField("offlinePlayer").setsArgumentAt(0)))
.method(forwardedMethods)
.intercept(forwarding)
.method(ElementMatchers.not(forwardedMethods))
.intercept(throwException)
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getDeclaredConstructor(OfflinePlayer.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Failed to find Player constructor!", e);
}
}
/**
* This interface extends both OfflinePlayer and Player (in that order) so that the class generated by ByteBuddy
* looks at OfflinePlayer's methods first while still being a Player.
*/
private interface PlayerUnion extends OfflinePlayer, Player
{
}
}
@@ -17,25 +17,23 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.primitives.Primitives;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/**
* Represents an object capable of converting wrapped Bukkit objects into NMS objects.
@@ -45,123 +43,136 @@ import com.google.common.primitives.Primitives;
* <li>org.bukkit.entity.Player to net.minecraft.server.EntityPlayer</li>
* <li>org.bukkit.World to net.minecraft.server.WorldServer</li>
* </ul>
*
*
* @author Kristian
*/
public class BukkitUnwrapper implements Unwrapper {
private static BukkitUnwrapper DEFAULT;
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
private static final Map<Class<?>, Unwrapper> UNWRAPPER_CACHE = new ConcurrentHashMap<Class<?>, Unwrapper>();
private static BukkitUnwrapper DEFAULT;
// The current error reporter
private final ErrorReporter reporter;
/**
* Retrieve the default instance of the Bukkit unwrapper.
* @return The default instance.
*/
public static BukkitUnwrapper getInstance() {
ErrorReporter currentReporter = ProtocolLibrary.getErrorReporter();
// Also recreate the unwrapper if the error reporter has changed
if (DEFAULT == null || DEFAULT.reporter != currentReporter) {
DEFAULT = new BukkitUnwrapper(currentReporter);
}
return DEFAULT;
}
/**
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
*/
public BukkitUnwrapper() {
this(ProtocolLibrary.getErrorReporter());
}
/**
* Construct a new Bukkit unwrapper with the given error reporter.
*
* @param reporter - the error reporter to use.
*/
public BukkitUnwrapper(ErrorReporter reporter) {
this.reporter = reporter;
this.reporter = reporter;
}
/**
* Retrieve the default instance of the Bukkit unwrapper.
*
* @return The default instance.
*/
public static BukkitUnwrapper getInstance() {
ErrorReporter currentReporter = ProtocolLibrary.getErrorReporter();
// Also recreate the unwrapper if the error reporter has changed
if (DEFAULT == null || DEFAULT.reporter != currentReporter) {
DEFAULT = new BukkitUnwrapper(currentReporter);
}
return DEFAULT;
}
private static Class<?> checkClass(Class<?> input, Class<?> expected, Class<?> result) {
if (expected.isAssignableFrom(input)) {
return result;
}
return null;
}
@SuppressWarnings("unchecked")
@Override
public Object unwrapItem(Object wrappedObject) {
// Special case
if (wrappedObject == null)
if (wrappedObject == null) {
return null;
}
Class<?> currentClass = PacketConstructor.getClass(wrappedObject);
// No need to unwrap primitives
if (currentClass.isPrimitive() || currentClass.equals(String.class))
if (currentClass.isPrimitive() || currentClass.equals(String.class)) {
return null;
}
// Next, check for types that doesn't have a getHandle()
if (wrappedObject instanceof Collection) {
return handleCollection((Collection<Object>) wrappedObject);
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
return null;
}
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
// Retrieve the handle
if (specificUnwrapper != null)
if (specificUnwrapper != null) {
return specificUnwrapper.unwrapItem(wrappedObject);
else
} else {
return null;
}
}
// Handle a collection of items
private Object handleCollection(Collection<Object> wrappedObject) {
@SuppressWarnings("unchecked")
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
if (copy != null) {
// Unwrap every element
for (Object element : wrappedObject) {
copy.add(unwrapItem(element));
}
return copy;
} else {
// Impossible
return null;
}
}
/**
* Retrieve a cached class unwrapper for the given class.
*
* @param type - the type of the class.
* @return An unwrapper for the given class.
*/
private Unwrapper getSpecificUnwrapper(final Class<?> type) {
// See if we're already determined this
if (unwrapperCache.containsKey(type)) {
if (UNWRAPPER_CACHE.containsKey(type)) {
// We will never remove from the cache, so this ought to be thread safe
return unwrapperCache.get(type);
return UNWRAPPER_CACHE.get(type);
}
try {
final Method find = type.getMethod("getHandle");
// It's thread safe, as getMethod should return the same handle
Unwrapper methodUnwrapper = new Unwrapper() {
@Override
public Object unwrapItem(Object wrappedObject) {
try {
if (wrappedObject instanceof Class)
if (wrappedObject instanceof Class) {
return checkClass((Class<?>) wrappedObject, type, find.getReturnType());
}
return find.invoke(wrappedObject);
} catch (IllegalArgumentException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
@@ -173,14 +184,14 @@ public class BukkitUnwrapper implements Unwrapper {
// This is really bad
throw new RuntimeException("Minecraft error.", e);
}
return null;
}
};
unwrapperCache.put(type, methodUnwrapper);
UNWRAPPER_CACHE.put(type, methodUnwrapper);
return methodUnwrapper;
} catch (SecurityException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
@@ -188,16 +199,18 @@ public class BukkitUnwrapper implements Unwrapper {
} catch (NoSuchMethodException e) {
// Maybe it's a proxy?
Unwrapper proxyUnwrapper = getProxyUnwrapper(type);
if (proxyUnwrapper != null)
if (proxyUnwrapper != null) {
return proxyUnwrapper;
}
// Try getting the field unwrapper too
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
if (fieldUnwrapper != null)
if (fieldUnwrapper != null) {
return fieldUnwrapper;
else
} else {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
}
}
// Default method
@@ -211,22 +224,19 @@ public class BukkitUnwrapper implements Unwrapper {
if (Player.class.isAssignableFrom(type)) {
final Method getHandle = MinecraftReflection.getCraftPlayerClass().getMethod("getHandle");
Unwrapper unwrapper = new Unwrapper() {
@Override
public Object unwrapItem(Object wrapped) {
Unwrapper unwrapper = wrapped -> {
try {
return getHandle.invoke(((Player) wrapped).getPlayer());
} catch (Throwable ex) {
try {
return getHandle.invoke(((Player) wrapped).getPlayer());
} catch (Throwable ex) {
try {
return getHandle.invoke(Bukkit.getPlayer(((Player) wrapped).getUniqueId()));
} catch (ReflectiveOperationException ex1) {
throw new RuntimeException("Failed to unwrap proxy " + wrapped, ex);
}
return getHandle.invoke(Bukkit.getPlayer(((Player) wrapped).getUniqueId()));
} catch (ReflectiveOperationException ex1) {
throw new RuntimeException("Failed to unwrap proxy " + wrapped, ex);
}
}
};
unwrapperCache.put(type, unwrapper);
UNWRAPPER_CACHE.put(type, unwrapper);
return unwrapper;
}
} catch (Throwable ignored) {
@@ -237,46 +247,42 @@ public class BukkitUnwrapper implements Unwrapper {
/**
* Retrieve a cached unwrapper using the handle field.
*
* @param type - a cached field unwrapper.
* @return The cached field unwrapper.
*/
private Unwrapper getFieldUnwrapper(final Class<?> type) {
final Field find = FieldUtils.getField(type, "handle", true);
// See if we succeeded
if (find != null) {
FieldAccessor accessor = Accessors.getFieldAccessorOrNull(type, "handle", null);
if (accessor != null) {
Unwrapper fieldUnwrapper = new Unwrapper() {
@Override
public Object unwrapItem(Object wrappedObject) {
try {
if (wrappedObject instanceof Class)
return checkClass((Class<?>) wrappedObject, type, find.getType());
return FieldUtils.readField(find, wrappedObject, true);
} catch (IllegalAccessException e) {
if (wrappedObject instanceof Class) {
return checkClass((Class<?>) wrappedObject, type, accessor.getField().getType());
}
return accessor.get(wrappedObject);
} catch (IllegalStateException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e)
.callerParam(wrappedObject, accessor.getField())
);
return null;
}
}
};
unwrapperCache.put(type, fieldUnwrapper);
UNWRAPPER_CACHE.put(type, fieldUnwrapper);
return fieldUnwrapper;
} else {
// Inform about this too
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(type)
);
return null;
}
}
private static Class<?> checkClass(Class<?> input, Class<?> expected, Class<?> result) {
if (expected.isAssignableFrom(input)) {
return result;
}
return null;
}
}
@@ -1,518 +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 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.*;
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;
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;
/**
* 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.getInstance().updateEntity(entity, observers);
}
@Override
public Entity getEntityFromID(World container, int id) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityFromID(container, id);
else
return EntityUtilities.getInstance().getEntityFromID(container, id);
}
@Override
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityTrackers(entity);
else
return EntityUtilities.getInstance().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,149 +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 org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
/**
* Represents a single delayed task.
*
* @author Kristian
*/
public class DelayedSingleTask {
protected int taskID = -1;
protected Plugin plugin;
protected BukkitScheduler scheduler;
protected boolean closed;
/**
* Create a single task scheduler.
* @param plugin - owner plugin.
*/
public DelayedSingleTask(Plugin plugin) {
this.plugin = plugin;
this.scheduler = plugin.getServer().getScheduler();
}
/**
* Create a single task scheduler.
* @param plugin - owner plugin.
* @param scheduler - specialized scheduler.
*/
public DelayedSingleTask(Plugin plugin, BukkitScheduler scheduler) {
this.plugin = plugin;
this.scheduler = scheduler;
}
/**
* Schedule a single task for execution.
* <p>
* Any previously scheduled task will be automatically cancelled.
* <p>
* Note that a tick delay of zero will execute the task immediately.
*
* @param ticksDelay - number of ticks before the task is executed.
* @param task - the task to schedule.
* @return TRUE if the task was successfully scheduled or executed, FALSE otherwise.
*/
public boolean schedule(long ticksDelay, Runnable task) {
if (ticksDelay < 0)
throw new IllegalArgumentException("Tick delay cannot be negative.");
if (task == null)
throw new IllegalArgumentException("task cannot be NULL");
if (closed)
return false;
// Special case
if (ticksDelay == 0) {
task.run();
return true;
}
// Boilerplate, boilerplate
final Runnable dispatch = task;
// Don't run multiple tasks!
cancel();
taskID = scheduler.scheduleSyncDelayedTask(plugin, new Runnable() {
@Override
public void run() {
dispatch.run();
taskID = -1;
}
}, ticksDelay);
return isRunning();
}
/**
* Whether or not a future task is scheduled to be executed.
* @return TRUE if a current task has been scheduled for execution, FALSE otherwise.
*/
public boolean isRunning() {
return taskID >= 0;
}
/**
* Cancel a future task from being executed.
* @return TRUE if a task was cancelled, FALSE otherwise.
*/
public boolean cancel() {
if (isRunning()) {
scheduler.cancelTask(taskID);
taskID = -1;
return true;
} else {
return false;
}
}
/**
* Retrieve the raw task ID.
* @return Raw task ID, or negative one if no task has been scheduled.
*/
public int getTaskID() {
return taskID;
}
/**
* Retrieve the plugin this task belongs to.
* @return The plugin scheduling the current taks.
*/
public Plugin getPlugin() {
return plugin;
}
/**
* Stop the current task and all future tasks scheduled by this instance.
*/
public synchronized void close() {
if (!closed) {
cancel();
plugin = null;
scheduler = null;
closed = true;
}
}
@Override
protected void finalize() throws Throwable {
close();
}
}
@@ -17,9 +17,6 @@
package com.comphenix.protocol.injector;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
@@ -27,10 +24,20 @@ import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
import com.google.common.collect.Lists;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.Validate;
import org.bukkit.World;
@@ -43,51 +50,106 @@ import org.bukkit.entity.Player;
* @author Kristian
*/
class EntityUtilities {
private static final boolean NEW_TRACKER = MinecraftVersion.VILLAGE_UPDATE.atOrAbove();
private static final EntityUtilities INSTANCE = new EntityUtilities();
private static final boolean NEW_TRACKER = MinecraftVersion.VILLAGE_UPDATE.atOrAbove();
private final Map<Class<?>, MethodAccessor> scanPlayersMethods = new ConcurrentHashMap<>();
private final Map<Class<?>, FieldAccessor> trackedEntityFields = new ConcurrentHashMap<>();
private FieldAccessor chunkMapField;
private FieldAccessor entityTrackerField;
private FieldAccessor trackedPlayersField;
private FieldAccessor trackedEntitiesField;
private MethodAccessor getEntity;
private MethodAccessor getChunkProvider;
private EntityUtilities() {
}
public static EntityUtilities getInstance() {
return INSTANCE;
}
private EntityUtilities() { }
private FieldAccessor entityTrackerField;
private FieldAccessor trackedEntitiesField;
private FieldAccessor trackedPlayersField;
private Map<Class<?>, MethodAccessor> scanPlayersMethods = new HashMap<>();
public void updateEntity(Entity entity, List<Player> observers) {
if (entity == null || !entity.isValid()) {
return;
}
Collection<?> trackedPlayers = getTrackedPlayers(entity);
List<Object> nmsPlayers = unwrapBukkit(observers);
Collection<?> trackedPlayers = this.getTrackedPlayers(entity);
List<Object> nmsPlayers = this.unwrapBukkit(observers);
trackedPlayers.removeAll(nmsPlayers);
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
List<Object> removingEntries =
MinecraftVersion.CAVES_CLIFFS_1.atOrAbove() ? this.getPlayerConnections(nmsPlayers) : nmsPlayers;
trackedPlayers.removeAll(removingEntries);
// there can be multiple different entity tracker entry impls, see GH-732....
scanPlayersMethods.computeIfAbsent(trackerEntry.getClass(), this::findScanPlayers).invoke(trackerEntry, nmsPlayers);
Object trackerEntry = this.getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
this.scanPlayersMethods.computeIfAbsent(trackerEntry.getClass(), this::findScanPlayers)
.invoke(trackerEntry, nmsPlayers);
}
public Entity getEntity(World world, int id) {
// new tracker registers the entity before sending out any packets - we can use the supported getter method here
if (NEW_TRACKER) {
Object level = BukkitUnwrapper.getInstance().unwrapItem(world);
if (this.getEntity == null) {
Method entityGetter = FuzzyReflection.fromObject(level).getMethodByReturnTypeAndParameters(
"getEntity",
MinecraftReflection.getEntityClass(),
int.class);
this.getEntity = Accessors.getMethodAccessor(entityGetter);
}
Object entity = this.getEntity.invoke(level, id);
return (Entity) MinecraftReflection.getBukkitEntity(entity);
}
// old tracker - now the pain begins
Object trackerEntry = this.getEntityTrackerEntry(world, id);
if (trackerEntry == null) {
// not tracked
return null;
}
FieldAccessor trackedEntityAccessor = this.trackedEntityFields.computeIfAbsent(trackerEntry.getClass(), clz -> {
try {
// try to get the first entity field from the tracker class
Field entityField = FuzzyReflection.fromClass(clz, true).getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getEntityClass())
.build());
return Accessors.getFieldAccessor(entityField);
} catch (IllegalArgumentException exception) {
// no such field, try the base tracker class instead
Class<?> trackerClass = MinecraftReflection.getEntityTrackerClass();
Field entityField = FuzzyReflection.fromClass(trackerClass, true).getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getEntityClass())
.build());
return Accessors.getFieldAccessor(entityField);
}
});
Object entity = trackedEntityAccessor.get(trackerEntry);
return (Entity) MinecraftReflection.getBukkitEntity(entity);
}
private MethodAccessor findScanPlayers(Class<?> trackerClass) {
MethodAccessor candidate = Accessors.getMethodAcccessorOrNull(trackerClass, "scanPlayers");
MethodAccessor candidate = Accessors.getMethodAccessorOrNull(trackerClass, "scanPlayers");
if (candidate != null) {
return candidate;
}
FuzzyReflection fuzzy = FuzzyReflection.fromClass(trackerClass, true);
return Accessors.getMethodAccessor(
fuzzy.getMethod(
FuzzyMethodContract.newBuilder().returnTypeVoid().parameterExactArray(List.class).build()));
return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.returnTypeVoid()
.parameterExactArray(List.class)
.build()));
}
/**
* 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.
@@ -98,11 +160,13 @@ class EntityUtilities {
}
List<Player> result = new ArrayList<>();
Collection<?> trackedPlayers = getTrackedPlayers(entity);
Collection<?> trackedPlayers = this.getTrackedPlayers(entity);
// Wrap every player - we also ensure that the underlying tracker list is immutable
for (Object tracker : trackedPlayers) {
if (MinecraftReflection.isMinecraftPlayer(tracker)) {
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove() && MinecraftReflection.isServerHandler(tracker)) {
result.add(MinecraftReflection.getBukkitPlayerFromConnection(tracker));
} else if (MinecraftReflection.isMinecraftPlayer(tracker)) {
result.add((Player) MinecraftReflection.getBukkitEntity(tracker));
}
}
@@ -113,56 +177,63 @@ class EntityUtilities {
private Collection<?> getTrackedPlayers(Entity entity) {
Validate.notNull(entity, "entity cannot be null");
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
Object trackerEntry = this.getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
Validate.notNull(trackerEntry, "Could not find entity trackers for " + entity);
if (trackedPlayersField == null) {
trackedPlayersField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*"));
if (this.trackedPlayersField == null) {
this.trackedPlayersField = Accessors.getFieldAccessor(
FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*"));
}
Validate.notNull(trackedPlayersField, "Could not find trackedPlayers field");
Validate.notNull(this.trackedPlayersField, "Could not find trackedPlayers field");
Object value = trackedPlayersField.get(trackerEntry);
Object value = this.trackedPlayersField.get(trackerEntry);
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());
throw new IllegalStateException(
"trackedPlayers field was an unknown type: expected Collection or Map, but got " + value.getClass());
}
}
private MethodAccessor getChunkProvider;
private FieldAccessor chunkMapField;
@SuppressWarnings("unchecked")
private Object getNewEntityTracker(Object worldServer, int entityId) {
if (getChunkProvider == null) {
Class<?> chunkProviderClass = MinecraftReflection.getMinecraftClass("ChunkProviderServer");
getChunkProvider = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(worldServer.getClass(), false).getMethod(
FuzzyMethodContract.newBuilder().parameterCount(0).returnTypeExact(chunkProviderClass).build()));
if (this.getChunkProvider == null) {
Class<?> chunkProviderClass = MinecraftReflection.getChunkProviderServer();
this.getChunkProvider = Accessors.getMethodAccessor(FuzzyReflection.fromClass(worldServer.getClass(), false)
.getMethod(FuzzyMethodContract.newBuilder().parameterCount(0).returnTypeExact(chunkProviderClass).build()));
}
Object chunkProvider = getChunkProvider.invoke(worldServer);
Object chunkProvider = this.getChunkProvider.invoke(worldServer);
if (chunkMapField == null) {
Class<?> chunkMapClass = MinecraftReflection.getMinecraftClass("PlayerChunkMap");
chunkMapField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(chunkProvider.getClass(), false).getField(
FuzzyFieldContract.newBuilder().typeExact(chunkMapClass).build()));
if (this.chunkMapField == null) {
Class<?> chunkMapClass = MinecraftReflection.getPlayerChunkMap();
this.chunkMapField = Accessors.getFieldAccessor(FuzzyReflection.fromClass(chunkProvider.getClass(), false)
.getField(FuzzyFieldContract.newBuilder().typeExact(chunkMapClass).build()));
}
Object playerChunkMap = chunkMapField.get(chunkProvider);
Object playerChunkMap = this.chunkMapField.get(chunkProvider);
if (trackedEntitiesField == null) {
trackedEntitiesField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField(
FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build()));
if (this.trackedEntitiesField == null) {
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
this.trackedEntitiesField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(playerChunkMap.getClass(), true)
.getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requirePublic()
.typeExact(MinecraftReflection.getInt2ObjectMapClass())
.build()));
} else {
this.trackedEntitiesField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField(
FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build()));
}
}
Map<Integer, Object> trackedEntities = (Map<Integer, Object>) trackedEntitiesField.get(playerChunkMap);
Map<Integer, Object> trackedEntities = (Map<Integer, Object>) this.trackedEntitiesField.get(playerChunkMap);
return trackedEntities.get(entityId);
}
@@ -171,97 +242,47 @@ class EntityUtilities {
Object worldServer = unwrapper.unwrapItem(world);
if (NEW_TRACKER) {
return getNewEntityTracker(worldServer, entityID);
return this.getNewEntityTracker(worldServer, entityID);
}
if (entityTrackerField == null)
entityTrackerField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(worldServer).
getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass()));
if (this.entityTrackerField == null) {
this.entityTrackerField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(worldServer).
getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass()));
}
// Get the tracker
Object tracker = entityTrackerField.get(worldServer);
Object tracker = this.entityTrackerField.get(worldServer);
// Looking for an IntHashMap in the tracker entry
if (trackedEntitiesField == null) {
trackedEntitiesField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(tracker, false)
if (this.trackedEntitiesField == null) {
this.trackedEntitiesField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(tracker, false)
.getFieldByType("trackedEntities", MinecraftReflection.getIntHashMapClass()));
}
// Read the map
Object trackedEntities = trackedEntitiesField.get(tracker);
Object trackedEntities = this.trackedEntitiesField.get(tracker);
return WrappedIntHashMap.fromHandle(trackedEntities).get(entityID);
}
private Map<Class<?>, FieldAccessor> trackerFields = new ConcurrentHashMap<>();
private MethodAccessor getEntityFromId;
/**
* Retrieve entity from a ID, even it it's newly created.
* @return The associated entity.
* @throws FieldAccessException Reflection error.
*/
public Entity getEntityFromID(World world, int entityID) {
Validate.notNull(world, "world cannot be null");
Validate.isTrue(entityID >= 0, "entityID cannot be negative");
try {
// first, try to read from the world
// this should be good enough for most cases, but only exists in 1.14+
if (NEW_TRACKER) {
Object worldServer = BukkitUnwrapper.getInstance().unwrapItem(world);
if (getEntityFromId == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(worldServer.getClass(), false);
getEntityFromId = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(int.class)
.returnTypeExact(MinecraftReflection.getEntityClass())
.build()));
}
Object entity = getEntityFromId.invoke(worldServer, entityID);
if (entity != null) {
return (Entity) MinecraftReflection.getBukkitEntity(entity);
}
}
// then go into the trackers
Object trackerEntry = getEntityTrackerEntry(world, entityID);
Object tracker = null;
if (trackerEntry != null) {
// plugins like citizens will use their own tracker
FieldAccessor trackerField = trackerFields.computeIfAbsent(trackerEntry.getClass(), x -> {
try {
return Accessors.getFieldAccessor(trackerEntry.getClass(), "tracker", true);
} catch (Exception e) {
// Assume it's the first entity field then
return Accessors.getFieldAccessor(FuzzyReflection.fromObject(trackerEntry, true)
.getFieldByType("tracker", MinecraftReflection.getEntityClass()));
}
});
tracker = trackerField.get(trackerEntry);
}
// If the tracker is NULL, we'll just assume this entity doesn't exist
return tracker != null ? (Entity) MinecraftReflection.getBukkitEntity(tracker) : null;
} catch (Exception e) {
throw new FieldAccessException("Cannot find entity from ID " + entityID + ".", e);
}
private List<Object> getPlayerConnections(List<Object> nmsPlayers) {
List<Object> connections = new ArrayList<>(nmsPlayers.size());
nmsPlayers.forEach(nmsPlayer -> connections.add(MinecraftFields.getPlayerConnection(nmsPlayer)));
return connections;
}
private List<Object> unwrapBukkit(List<Player> players) {
List<Object> output = Lists.newArrayList();
final List<Object> output = new ArrayList<>();
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
// Get the NMS equivalent
for (Player player : players) {
Object result = unwrapper.unwrapItem(player);
if (result != null)
if (result != null) {
output.add(result);
else
} else {
throw new IllegalArgumentException("Cannot unwrap item " + player);
}
}
return output;
@@ -2,16 +2,16 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
@@ -19,7 +19,7 @@ package com.comphenix.protocol.injector;
/**
* The current player phase. This is used to limit the number of different injections.
*
*
* @author Kristian
*/
public enum GamePhase {
@@ -27,27 +27,29 @@ public enum GamePhase {
* Only listen for packets sent or received before a player has logged in.
*/
LOGIN,
/**
* Only listen for packets sent or received after a player has logged in.
*/
PLAYING,
/**
* Listen for every sent and received packet.
*/
BOTH;
/**
* Determine if the current value represents the login phase.
*
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasLogin() {
return this == LOGIN || this == BOTH;
}
/**
* Determine if the current value represents the playing phase.
*
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasPlaying() {
@@ -1,49 +1,40 @@
package com.comphenix.protocol.injector;
import com.comphenix.protocol.ProtocolManager;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.ProtocolManager;
/**
* Yields access to the internal hook configuration.
*
*
* @author Kristian
*/
public interface InternalManager extends ProtocolManager {
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
public PlayerInjectHooks getPlayerHook();
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
public void setPlayerHook(PlayerInjectHooks playerHook);
/**
* Register this protocol manager on Bukkit.
*
* @param manager - Bukkit plugin manager that provides player join/leave events.
* @param plugin - the parent plugin.
* @param plugin - the parent plugin.
*/
public void registerEvents(PluginManager manager, final Plugin plugin);
void registerEvents(PluginManager manager, final Plugin plugin);
/**
* Called when ProtocolLib is closing.
*/
public void close();
void close();
/**
* Determine if debug mode is enabled.
*
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isDebug();
boolean isDebug();
/**
* Set whether or not debug mode is enabled.
*
* @param debug - TRUE if it is, FALSE otherwise.
*/
public void setDebug(boolean debug);
void setDebug(boolean debug);
}
@@ -2,16 +2,16 @@
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
@@ -19,76 +19,33 @@ package com.comphenix.protocol.injector;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.packet.InterceptWritePacket;
/**
* Represents an object that initiate the packet listeners.
*
*
* @author Kristian
*/
public interface ListenerInvoker {
/**
* Invokes the given packet event for every registered listener.
*
* @param event - the packet event to invoke.
*/
public abstract void invokePacketRecieving(PacketEvent event);
void invokePacketReceiving(PacketEvent event);
/**
* Invokes the given packet event for every registered listener.
*
* @param event - the packet event to invoke.
*/
public abstract void invokePacketSending(PacketEvent event);
/**
* Retrieve the associated ID of a packet.
* @param packet - the packet.
* @return The packet ID.
*/
@Deprecated
public abstract int getPacketID(Object packet);
void invokePacketSending(PacketEvent event);
/**
* Retrieve the associated type of a packet.
*
* @param packet - the packet.
* @return The packet type.
*/
public abstract PacketType getPacketType(Object packet);
/**
* Retrieve the object responsible for intercepting write packets.
* @return Object that intercepts write packets.
*/
public InterceptWritePacket getInterceptWritePacket();
/**
* Determine if a given packet requires input buffering.
* @param packetId - the packet to check.
* @return TRUE if it does, FALSE otherwise.
*/
@Deprecated
public boolean requireInputBuffer(int packetId);
/**
* Associate a given class with the given packet ID. Internal method.
* @param clazz - class to associate.
*/
public abstract void unregisterPacketClass(Class<?> clazz);
/**
* Register a given class in the packet registry. Internal method.
* @param clazz - class to register.
* @param packetID - the the new associated packet ID.
*/
@Deprecated
public abstract void registerPacketClass(Class<?> clazz, int packetID);
/**
* 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.
*/
@Deprecated
public abstract Class<?> getPacketClassFromID(int packetID, boolean forceVanilla);
PacketType getPacketType(Object packet);
}
@@ -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,108 +1,74 @@
package com.comphenix.protocol.injector;
import java.util.List;
import java.util.PriorityQueue;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketOutputHandler;
import com.comphenix.protocol.events.PacketPostListener;
import com.comphenix.protocol.events.ScheduledPacket;
import java.util.Deque;
/**
* Represents a processor for network markers.
*
* @author Kristian
*/
public class NetworkProcessor {
private ErrorReporter reporter;
private final ErrorReporter reporter;
/**
* Construct a new network processor.
*
* @param reporter - the reporter.
*/
public NetworkProcessor(ErrorReporter reporter) {
this.reporter = reporter;
}
/**
* Process the serialized packet byte array with the given network marker.
* @param event - current packet event.
* @param marker - the network marker.
* @param input - the input array.
* @return The output array.
*/
public byte[] processOutput(PacketEvent event, NetworkMarker marker, final byte[] input) {
// Bit of a hack - but we need the performance
PriorityQueue<PacketOutputHandler> handlers = (PriorityQueue<PacketOutputHandler>)
marker.getOutputHandlers();
byte[] output = input;
// Let each handler prepare the actual output
while (!handlers.isEmpty()) {
PacketOutputHandler handler = handlers.poll();
try {
byte[] changed = handler.handle(event, output);
// Don't break just because a plugin returned NULL
if (changed != null) {
output = changed;
} else {
throw new IllegalStateException("Handler cannot return a NULL array.");
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportMinimal(handler.getPlugin(), "PacketOutputHandler.handle()", e);
}
}
return output;
}
/**
* Invoke the post listeners and packet transmission, if any.
* @param event - PacketEvent
*
* @param event - PacketEvent
* @param marker - the network marker, or NULL.
*/
public void invokePostEvent(PacketEvent event, NetworkMarker marker) {
if (marker == null)
if (marker == null) {
return;
if (NetworkMarker.hasPostListeners(marker)) {
}
if (event != null && NetworkMarker.hasPostListeners(marker)) {
// Invoke every sent listener
for (PacketPostListener listener : marker.getPostListeners()) {
try {
listener.onPostEvent(event);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
} catch (OutOfMemoryError | ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e);
this.reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e);
}
}
}
sendScheduledPackets(marker);
this.sendScheduledPackets(marker);
}
/**
* Send any scheduled packets.
*
* @param marker - the network marker.
*/
private void sendScheduledPackets(NetworkMarker marker) {
// Next, invoke post packet transmission
List<ScheduledPacket> scheduled = NetworkMarker.readScheduledPackets(marker);
Deque<ScheduledPacket> scheduled = NetworkMarker.readScheduledPackets(marker);
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
if (scheduled != null) {
for (ScheduledPacket packet : scheduled) {
ScheduledPacket packet;
while ((packet = scheduled.poll()) != null) {
packet.schedule(manager);
}
}
}
}
}
@@ -17,10 +17,6 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.error.RethrowErrorReporter;
import com.comphenix.protocol.events.PacketContainer;
@@ -30,70 +26,117 @@ import com.comphenix.protocol.wrappers.BukkitConverters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.primitives.Primitives;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
/**
* A packet constructor that uses an internal Minecraft.
* @author Kristian
*
* @author Kristian
*/
public class PacketConstructor {
/**
* A packet constructor that automatically converts Bukkit types to their NMS conterpart.
* <p>
* Remember to call withPacket().
*/
public static PacketConstructor DEFAULT = new PacketConstructor(null);
// The constructor method that's actually responsible for creating the packet
private Constructor<?> constructorMethod;
private final Constructor<?> constructorMethod;
// Used to unwrap Bukkit objects
private final List<Unwrapper> unwrappers;
// The packet ID
private PacketType type;
// Used to unwrap Bukkit objects
private List<Unwrapper> unwrappers;
// Parameters that need to be unwrapped
private Unwrapper[] paramUnwrapper;
private PacketConstructor(Constructor<?> constructorMethod) {
this.constructorMethod = constructorMethod;
this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper(new RethrowErrorReporter() ));
this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper(new RethrowErrorReporter()));
this.unwrappers.addAll(BukkitConverters.getUnwrappers());
}
private PacketConstructor(PacketType type, Constructor<?> constructorMethod, List<Unwrapper> unwrappers, Unwrapper[] paramUnwrapper) {
private PacketConstructor(PacketType type, Constructor<?> constructorMethod, List<Unwrapper> unwrappers,
Unwrapper[] paramUnwrapper) {
this.type = type;
this.constructorMethod = constructorMethod;
this.unwrappers = unwrappers;
this.paramUnwrapper = paramUnwrapper;
}
// Determine if a method with the types 'params' can be called with 'types'
private static boolean isCompatible(Class<?>[] types, Class<?>[] params) {
// Determine if the types are similar
if (params.length == types.length) {
for (int i = 0; i < params.length; i++) {
Class<?> inputType = types[i];
Class<?> paramType = params[i];
// The input type is always wrapped
if (!inputType.isPrimitive() && paramType.isPrimitive()) {
// Wrap it
paramType = Primitives.wrap(paramType);
}
// Compare assignability
if (!paramType.isAssignableFrom(inputType)) {
return false;
}
}
return true;
}
// Parameter count must match
return false;
}
/**
* Retrieve the class of an object, or just the class if it already is a class object.
*
* @param obj - the object.
* @return The class of an object.
*/
public static Class<?> getClass(Object obj) {
if (obj instanceof Class) {
return (Class<?>) obj;
}
return obj.getClass();
}
public ImmutableList<Unwrapper> getUnwrappers() {
return ImmutableList.copyOf(unwrappers);
}
/**
* Retrieve the id of the packets this constructor creates.
* <p>
* Deprecated: Use {@link #getType()} instead.
*
* @return The ID of the packets this constructor will create.
*/
@Deprecated
public int getPacketID() {
return type.getLegacyId();
return type.getCurrentId();
}
/**
* Retrieve the type of the packets this constructor creates.
*
* @return The type of the created packets.
*/
public PacketType getType() {
return type;
}
/**
* Return a copy of the current constructor with a different list of unwrappers.
*
* @param unwrappers - list of unwrappers that convert Bukkit wrappers into the equivalent NMS classes.
* @return A constructor with a different set of unwrappers.
*/
@@ -101,27 +144,12 @@ public class PacketConstructor {
return new PacketConstructor(type, constructorMethod, unwrappers, paramUnwrapper);
}
/**
* Create a packet constructor that creates packets using the given ID.
* <p>
* Note that if you pass a Class as a value, it will use its type directly.
* <p>
* Deprecated: Use {@link #withPacket(PacketType, Object[])} instead.
* @param id - legacy (1.6.4) packet ID.
* @param values - the values that will match each parameter in the desired constructor.
* @return A packet constructor with these types.
* @throws IllegalArgumentException If no packet constructor could be created with these types.
*/
@Deprecated
public PacketConstructor withPacket(int id, Object[] values) {
return withPacket(PacketType.findLegacy(id), values);
}
/**
* Create a packet constructor that creates packets using the given types.
* <p>
* Note that if you pass a Class as a value, it will use its type directly.
* @param type - the type of the packet to create.
*
* @param type - the type of the packet to create.
* @param values - the values that will match each parameter in the desired constructor.
* @return A packet constructor with these types.
* @throws IllegalArgumentException If no packet constructor could be created with these types.
@@ -130,15 +158,15 @@ public class PacketConstructor {
Class<?>[] types = new Class<?>[values.length];
Throwable lastException = null;
Unwrapper[] paramUnwrapper = new Unwrapper[values.length];
for (int i = 0; i < types.length; i++) {
// Default type
if (values[i] != null) {
types[i] = PacketConstructor.getClass(values[i]);
for (Unwrapper unwrapper : unwrappers) {
Object result = null;
try {
result = unwrapper.unwrapItem(values[i]);
} catch (OutOfMemoryError e) {
@@ -148,7 +176,7 @@ public class PacketConstructor {
} catch (Throwable e) {
lastException = e;
}
// Update type we're searching for
if (result != null) {
types[i] = PacketConstructor.getClass(result);
@@ -156,19 +184,17 @@ public class PacketConstructor {
break;
}
}
} else {
// Try it
types[i] = Object.class;
}
}
Class<?> packetType = PacketRegistry.getPacketClassFromType(type, true);
if (packetType == null)
throw new IllegalArgumentException("Could not find a packet by the type " + type);
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
// Find the correct constructor
for (Constructor<?> constructor : packetType.getConstructors()) {
for (Constructor<?> constructor : packetClass.getConstructors()) {
Class<?>[] params = constructor.getParameterTypes();
if (isCompatible(types, params)) {
@@ -178,14 +204,15 @@ public class PacketConstructor {
}
throw new IllegalArgumentException("No suitable constructor could be found.", lastException);
}
/**
* Construct a packet using the special builtin Minecraft constructors.
*
* @param values - values containing Bukkit wrapped items to pass to Minecraft.
* @return The created packet.
* @throws FieldAccessException Failure due to a security limitation.
* @throws FieldAccessException Failure due to a security limitation.
* @throws IllegalArgumentException Arguments doesn't match the constructor.
* @throws RuntimeException Minecraft threw an exception.
* @throws RuntimeException Minecraft threw an exception.
*/
public PacketContainer createPacket(Object... values) throws FieldAccessException {
try {
@@ -195,10 +222,10 @@ public class PacketConstructor {
values[i] = paramUnwrapper[i].unwrapItem(values[i]);
}
}
Object nmsPacket = constructorMethod.newInstance(values);
return new PacketContainer(type, nmsPacket);
} catch (IllegalArgumentException e) {
throw e;
} catch (InstantiationException e) {
@@ -209,57 +236,20 @@ public class PacketConstructor {
throw new RuntimeException("Minecraft error.", e);
}
}
// Determine if a method with the types 'params' can be called with 'types'
private static boolean isCompatible(Class<?>[] types, Class<?>[] params) {
// Determine if the types are similar
if (params.length == types.length) {
for (int i = 0; i < params.length; i++) {
Class<?> inputType = types[i];
Class<?> paramType = params[i];
// The input type is always wrapped
if (!inputType.isPrimitive() && paramType.isPrimitive()) {
// Wrap it
paramType = Primitives.wrap(paramType);
}
// Compare assignability
if (!paramType.isAssignableFrom(inputType)) {
return false;
}
}
return true;
}
// Parameter count must match
return false;
}
/**
* Retrieve the class of an object, or just the class if it already is a class object.
* @param obj - the object.
* @return The class of an object.
*/
public static Class<?> getClass(Object obj) {
if (obj instanceof Class)
return (Class<?>) obj;
return obj.getClass();
}
/**
* Represents a unwrapper for a constructor parameter.
*
*
* @author Kristian
*/
public static interface Unwrapper {
/**
* Convert the given wrapped object to the equivalent net.minecraft.server object.
* <p>
* Note that we may pass in a class instead of object - in that case, the unwrapper should
* return the equivalent NMS class.
* Note that we may pass in a class instead of object - in that case, the unwrapper should return the equivalent NMS
* class.
*
* @param wrappedObject - wrapped object or class.
* @return The equivalent net.minecraft.server object or class.
*/
@@ -1,256 +1,122 @@
package com.comphenix.protocol.injector;
import javax.annotation.Nonnull;
import org.bukkit.Server;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.plugin.Plugin;
import com.comphenix.executors.BukkitFutures;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.player.InjectedServerConnection;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import javax.annotation.Nonnull;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
public class PacketFilterBuilder {
public static final ReportType REPORT_TEMPORARY_EVENT_ERROR = new ReportType("Unable to register or handle temporary event.");
public static final ReportType REPORT_SPIGOT_IS_DELAYING_INJECTOR = new ReportType("Delaying due to Spigot.");
private ClassLoader classLoader;
private Server server;
private Plugin library;
private MinecraftVersion mcVersion;
private DelayedSingleTask unhookTask;
private ErrorReporter reporter;
// Whether or not we need to enable Netty
private AsyncFilterManager asyncManager;
private boolean nettyEnabled;
/**
* Update the current class loader.
* @param classLoader - current class loader.
* @return This builder, for chaining.
*/
public PacketFilterBuilder classLoader(@Nonnull ClassLoader classLoader) {
if (classLoader == null)
throw new IllegalArgumentException("classLoader cannot be NULL.");
this.classLoader = classLoader;
return this;
}
/**
* Set the current server.
*
* @param server - current server.
* @return This builder, for chaining.
*/
public PacketFilterBuilder server(@Nonnull Server server) {
if (server == null)
throw new IllegalArgumentException("server cannot be NULL.");
this.server = server;
return this;
}
/**
* Set a reference to the plugin instance of ProtocolLib.
*
* @param library - plugin instance.
* @return This builder, for chaining.
*/
public PacketFilterBuilder library(@Nonnull Plugin library) {
if (library == null)
throw new IllegalArgumentException("library cannot be NULL.");
this.library = library;
return this;
}
/**
* Set the current Minecraft version.
*
* @param mcVersion - Minecraft version.
* @return This builder, for chaining.
* @return This builder, for chaining.
*/
public PacketFilterBuilder minecraftVersion(@Nonnull MinecraftVersion mcVersion) {
if (mcVersion == null)
throw new IllegalArgumentException("minecraftVersion cannot be NULL.");
this.mcVersion = mcVersion;
return this;
}
/**
* Set the task used to delay unhooking when ProtocolLib is no in use.
* @param unhookTask - the unhook task.
* @return This builder, for chaining.
*/
public PacketFilterBuilder unhookTask(@Nonnull DelayedSingleTask unhookTask) {
if (unhookTask == null)
throw new IllegalArgumentException("unhookTask cannot be NULL.");
this.unhookTask = unhookTask;
return this;
}
/**
* Set the error reporter.
*
* @param reporter - new error reporter.
* @return This builder, for chaining.
*/
public PacketFilterBuilder reporter(@Nonnull ErrorReporter reporter) {
if (reporter == null)
throw new IllegalArgumentException("reporter cannot be NULL.");
this.reporter = reporter;
return this;
}
/**
* Determine if we should prepare to hook Netty in Spigot.
* <p>
* This is calculated in the {@link #build()} method.
* @return TRUE if we should, FALSE otherwise.
*/
public boolean isNettyEnabled() {
return nettyEnabled;
}
/**
* Retrieve the class loader set in this builder.
* @return The class loader.
*/
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* Retrieve the current CraftBukkit server.
*
* @return Current server.
*/
public Server getServer() {
return server;
return this.server;
}
/**
* Retrieve a reference to the current ProtocolLib instance.
*
* @return ProtocolLib.
*/
public Plugin getLibrary() {
return library;
return this.library;
}
/**
* Retrieve the current Minecraft version.
*
* @return Current version.
*/
public MinecraftVersion getMinecraftVersion() {
return mcVersion;
}
/**
* Retrieve the task that is used to delay unhooking when ProtocolLib is no in use.
* @return The unhook task.
*/
public DelayedSingleTask getUnhookTask() {
return unhookTask;
return this.mcVersion;
}
/**
* Retrieve the error reporter.
*
* @return Error reporter.
*/
public ErrorReporter getReporter() {
return reporter;
return this.reporter;
}
/**
* Retrieve the asynchronous manager.
* <p>
* This is first constructed the {@link #build()} method.
*
* @return The asynchronous manager.
*/
public AsyncFilterManager getAsyncManager() {
return asyncManager;
return this.asyncManager;
}
/**
* Create a new packet filter manager.
*
* @return A new packet filter manager.
*/
public InternalManager build() {
if (reporter == null)
if (this.reporter == null) {
throw new IllegalArgumentException("reporter cannot be NULL.");
if (classLoader == null)
throw new IllegalArgumentException("classLoader cannot be NULL.");
asyncManager = new AsyncFilterManager(reporter, server.getScheduler());
nettyEnabled = false;
// Spigot
if (SpigotPacketInjector.canUseSpigotListener()) {
// If the server hasn't loaded yet - wait
if (InjectedServerConnection.getServerConnection(reporter, server) == null) {
// We need to delay this until we know if Netty is enabled
final DelayedPacketManager delayed = new DelayedPacketManager(reporter, mcVersion);
// They must reference each other
delayed.setAsynchronousManager(asyncManager);
asyncManager.setManager(delayed);
Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class),
new FutureCallback<WorldInitEvent>() {
@Override
public void onSuccess(WorldInitEvent event) {
// Nevermind
if (delayed.isClosed())
return;
try {
registerSpigot(delayed);
} catch (Exception e) {
onFailure(e);
}
}
@Override
public void onFailure(Throwable error) {
reporter.reportWarning(PacketFilterBuilder.this, Report
.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error));
}
});
reporter.reportWarning(this, Report.newBuilder(REPORT_SPIGOT_IS_DELAYING_INJECTOR));
// Let plugins use this version instead
return delayed;
} else {
nettyEnabled = !MinecraftReflection.isMinecraftObject(
InjectedServerConnection.getServerConnection(reporter, server));
}
}
// Otherwise - construct the packet filter manager right away
return buildInternal();
}
private void registerSpigot(DelayedPacketManager delayed) {
// Use netty if we have a non-standard ServerConnection class
nettyEnabled = !MinecraftReflection.isMinecraftObject(
InjectedServerConnection.getServerConnection(reporter, server));
// Switch to the standard manager
delayed.setDelegate(buildInternal());
}
/**
* Construct a new packet filter manager without checking for Netty.
* @return A new packet filter manager.
*/
private PacketFilterManager buildInternal() {
PacketFilterManager manager = new PacketFilterManager(this);
// It's a cyclic reference, but it's too late to fix now
asyncManager.setManager(manager);
return manager;
this.asyncManager = new AsyncFilterManager(this.reporter, this.server.getScheduler());
return new PacketFilterManager(this);
}
}
File diff suppressed because it is too large. Load diff
@@ -1,31 +0,0 @@
package com.comphenix.protocol.injector;
/**
* Sets the inject hook type. Different types allow for maximum compatibility.
* @author Kristian
*/
public enum PlayerInjectHooks {
/**
* The injection hook that does nothing. Set when every other inject hook fails.
*/
NONE,
/**
* Override the network handler object itself. Only works in 1.3.
* <p>
* Cannot intercept MapChunk packets.
*/
NETWORK_MANAGER_OBJECT,
/**
* Override the packet queue lists in NetworkHandler.
* <p>
* Cannot intercept MapChunk packets.
*/
NETWORK_HANDLER_FIELDS,
/**
* Override the server handler object. Versatile, but a tad slower.
*/
NETWORK_SERVER_OBJECT;
}
@@ -1,58 +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;
/**
* Invoked when attempting to use a player that has already logged out.
*
* @author Kristian
*/
public class PlayerLoggedOutException extends RuntimeException {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 4889257862160145234L;
public PlayerLoggedOutException() {
// Default error message
super("Cannot inject a player that has already logged out.");
}
public PlayerLoggedOutException(String message, Throwable cause) {
super(message, cause);
}
public PlayerLoggedOutException(String message) {
super(message);
}
public PlayerLoggedOutException(Throwable cause) {
super(cause);
}
/**
* Construct an exception from a formatted message.
* @param message - the message to format.
* @param params - parameters.
* @return The formated exception
*/
public static PlayerLoggedOutException fromFormat(String message, Object... params) {
return new PlayerLoggedOutException(String.format(message, params));
}
}
@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import com.google.common.collect.ImmutableSet;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginLoadOrder;
@@ -55,12 +56,12 @@ class PluginVerifier {
/**
* Contains a list of plugins that will detect and use ProtocolLib dynamically, instead of relying on the dependency system.
*/
private static final Set<String> DYNAMIC_DEPENDENCY = Sets.newHashSet("mcore", "MassiveCore");
private static final Set<String> DYNAMIC_DEPENDENCY = ImmutableSet.of("mcore", "MassiveCore");
/**
* Set of plugins that have been loaded after ProtocolLib.
*/
private final Set<String> loadedAfter = new HashSet<String>();
private final Set<String> loadedAfter = new HashSet<>();
/**
* Reference to ProtocolLib.
@@ -201,7 +202,7 @@ class PluginVerifier {
if (list == null)
return Collections.emptySet();
else
return Sets.newHashSet(list);
return new HashSet<>(list);
}
// Avoid cycles. DFS.
@@ -50,7 +50,7 @@ public class PrioritizedListener<TListener> implements Comparable<PrioritizedLis
@Override
public boolean equals(Object obj) {
// We only care about the listener - priority itself should not make a difference
if(obj instanceof PrioritizedListener){
if (obj instanceof PrioritizedListener) {
final PrioritizedListener<TListener> other = (PrioritizedListener<TListener>) obj;
return Objects.equal(listener, other.listener);
} else {
@@ -17,161 +17,186 @@
package com.comphenix.protocol.injector;
import java.util.HashSet;
import java.util.Set;
import java.security.PublicKey;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.compiler.CompileListener;
import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.ZeroBuffer;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;
/**
* Caches structure modifiers.
*
* @author Kristian
*/
public class StructureCache {
// Structure modifiers
private static ConcurrentMap<PacketType, StructureModifier<Object>> structureModifiers =
new ConcurrentHashMap<PacketType, StructureModifier<Object>>();
private static final Map<Class<?>, Supplier<Object>> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>();
private static final Map<PacketType, StructureModifier<Object>> STRUCTURE_MODIFIER_CACHE = new ConcurrentHashMap<>();
private static Set<PacketType> compiling = new HashSet<PacketType>();
// packet data serializer which always returns an empty nbt tag compound
private static boolean TRICK_TRIED = false;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_BASE;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_JSON;
/**
* Creates an empty Minecraft packet of the given id.
* <p>
* Decreated: Use {@link #newPacket(PacketType)} instead.
* @param legacyId - legacy (1.6.4) packet id.
* @return Created packet.
*/
@Deprecated
public static Object newPacket(int legacyId) {
return newPacket(PacketType.findLegacy(legacyId));
public static Object newPacket(Class<?> packetClass) {
Supplier<Object> packetConstructor = PACKET_INSTANCE_CREATORS.computeIfAbsent(packetClass, clazz -> {
// prefer construction via PacketDataSerializer constructor on 1.17 and above
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
ConstructorAccessor serializerAccessor = Accessors.getConstructorAccessorOrNull(
clazz,
MinecraftReflection.getPacketDataSerializerClass());
if (serializerAccessor != null) {
// check if the method is possible
if (tryInitTrickDataSerializer()) {
try {
// first try with the base accessor
Object serializer = TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
serializerAccessor.invoke(serializer); // throwaway instance, for testing
// method is working
return () -> serializerAccessor.invoke(serializer);
} catch (Exception exception) {
try {
// try with the json accessor
Object serializer = TRICKED_DATA_SERIALIZER_JSON.invoke(new ZeroBuffer());
serializerAccessor.invoke(serializer); // throwaway instance, for testing
// method is working
return () -> serializerAccessor.invoke(serializer);
} catch (Exception ignored) {
// shrug, fall back to default behaviour
}
}
}
}
}
// try via DefaultInstances as fallback
return () -> {
Object packetInstance = DefaultInstances.DEFAULT.create(clazz);
Objects.requireNonNull(packetInstance, "Unable to create packet instance for class " + clazz);
return packetInstance;
};
});
return packetConstructor.get();
}
/**
* Creates an empty Minecraft packet of the given type.
*
* @param type - packet type.
* @return Created packet.
*/
public static Object newPacket(PacketType type) {
Class<?> clazz = PacketRegistry.getPacketClassFromType(type, true);
// Check the return value
if (clazz != null) {
// TODO: Optimize DefaultInstances
Object result = DefaultInstances.DEFAULT.create(clazz);
if (result == null) {
throw new IllegalArgumentException("Failed to create packet for type: " + type);
}
return result;
}
throw new IllegalArgumentException("Cannot find associated packet class: " + type);
}
/**
* Retrieve a cached structure modifier for the given packet id.
* <p>
* Deprecated: Use {@link #getStructure(PacketType)} instead.
* @param legacyId - the legacy (1.6.4) packet ID.
* @return A structure modifier.
*/
@Deprecated
public static StructureModifier<Object> getStructure(int legacyId) {
return getStructure(PacketType.findLegacy(legacyId));
}
/**
* Retrieve a cached structure modifier for the given packet type.
* @param type - packet type.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(PacketType type) {
// Compile structures by default
return getStructure(type, true);
return newPacket(PacketRegistry.getPacketClassFromType(type));
}
/**
* Retrieve a cached structure modifier given a packet type.
*
* @param packetType - packet type.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(Class<?> packetType) {
// Compile structures by default
return getStructure(packetType, true);
}
/**
* Retrieve a cached structure modifier given a packet type.
* @param packetType - packet type.
* @param compile - whether or not to asynchronously compile the structure modifier.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(Class<?> packetType, boolean compile) {
// Get the ID from the class
return getStructure(PacketRegistry.getPacketType(packetType), compile);
}
/**
* Retrieve a cached structure modifier for the given packet ID.
* <p>
* Deprecated: Use {@link #getStructure(PacketType, boolean)} instead.
* @param legacyId - the legacy (1.6.4) packet ID.
* @param compile - whether or not to asynchronously compile the structure modifier.
* @return A structure modifier.
*/
@Deprecated
public static StructureModifier<Object> getStructure(final int legacyId, boolean compile) {
return getStructure(PacketType.findLegacy(legacyId), compile);
PacketType type = PacketRegistry.getPacketType(packetType);
Preconditions.checkNotNull(type, "No packet type associated with " + packetType);
return getStructure(type);
}
/**
* Retrieve a cached structure modifier for the given packet type.
* @param type - packet type.
* @param compile - whether or not to asynchronously compile the structure modifier.
*
* @param packetType - packet type.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(final PacketType type, boolean compile) {
StructureModifier<Object> result = structureModifiers.get(type);
public static StructureModifier<Object> getStructure(final PacketType packetType) {
Preconditions.checkNotNull(packetType, "type cannot be null");
// We don't want to create this for every lookup
if (result == null) {
// Use the vanilla class definition
final StructureModifier<Object> value = new StructureModifier<Object>(
PacketRegistry.getPacketClassFromType(type, true), MinecraftReflection.getPacketClass(), true);
return STRUCTURE_MODIFIER_CACHE.computeIfAbsent(packetType, type -> {
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
return new StructureModifier<>(packetClass, MinecraftReflection.getPacketClass(), true);
});
}
result = structureModifiers.putIfAbsent(type, value);
/**
* Returns a new mocked null data serializer instance, if possible.
*
* @return a new null data serializer instance.
*/
public static Object newNullDataSerializer() {
tryInitTrickDataSerializer();
return TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
}
// We may end up creating multiple modifiers, but we'll agree on which to use
if (result == null) {
result = value;
}
/**
* Creates a packet data serializer sub-class if needed to allow the fixed read of a NbtTagCompound because of a
* null check in the MapChunk packet constructor.
*/
public static boolean tryInitTrickDataSerializer() {
if (TRICK_TRIED) {
return TRICKED_DATA_SERIALIZER_BASE != null;
}
// Automatically compile the structure modifier
if (compile && !(result instanceof CompiledStructureModifier)) {
// Compilation is many orders of magnitude slower than synchronization
synchronized (compiling) {
final BackgroundCompiler compiler = BackgroundCompiler.getInstance();
// prevent double init
TRICK_TRIED = true;
if (!compiling.contains(type) && compiler != null) {
compiler.scheduleCompilation(result, new CompileListener<Object>() {
@Override
public void onCompiled(StructureModifier<Object> compiledModifier) {
structureModifiers.put(type, compiledModifier);
}
});
compiling.add(type);
}
}
try {
// create an empty instance of a nbt tag compound / text compound that we can re-use when needed
Object textCompound = WrappedChatComponent.fromText("").getHandle();
Object compound = Accessors.getConstructorAccessor(MinecraftReflection.getNBTCompoundClass()).invoke();
// base builder which intercepts a few methods
DynamicType.Builder<?> baseBuilder = ByteBuddyFactory.getInstance()
.createSubclass(MinecraftReflection.getPacketDataSerializerClass())
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerBase")
.method(ElementMatchers.returns(MinecraftReflection.getNBTCompoundClass())
.and(ElementMatchers.takesArguments(MinecraftReflection.getNBTReadLimiterClass())))
.intercept(FixedValue.value(compound))
.method(ElementMatchers.returns(MinecraftReflection.getIChatBaseComponentClass()))
.intercept(FixedValue.value(textCompound))
.method(ElementMatchers.returns(PublicKey.class).and(ElementMatchers.takesNoArguments()))
.intercept(FixedValue.nullValue());
Class<?> serializerBase = baseBuilder.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
.getLoaded();
TRICKED_DATA_SERIALIZER_BASE = Accessors.getConstructorAccessor(serializerBase, ByteBuf.class);
// extended builder which intercepts the read string method as well
Class<?> withStringIntercept = baseBuilder
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerJson")
.method(ElementMatchers.returns(String.class).and(ElementMatchers.takesArguments(int.class)))
.intercept(FixedValue.value("{}"))
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
.getLoaded();
TRICKED_DATA_SERIALIZER_JSON = Accessors.getConstructorAccessor(withStringIntercept, ByteBuf.class);
// worked
return true;
} catch (Exception ignored) {
}
return result;
// didn't work
return false;
}
}
@@ -1,234 +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 io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.Callable;
import com.google.common.collect.Lists;
public class BootstrapList implements List<Object> {
private List<Object> delegate;
private ChannelHandler handler;
/**
* Construct a new bootstrap list.
* @param delegate - the delegate.
* @param handler - the channel handler to add.
*/
public BootstrapList(List<Object> delegate, ChannelHandler handler) {
this.delegate = delegate;
this.handler = handler;
// Process all existing bootstraps
for (Object item : this) {
processElement(item);
}
}
@Override
public synchronized boolean add(Object element) {
processElement(element);
return delegate.add(element);
}
@Override
public synchronized boolean addAll(Collection<? extends Object> collection) {
List<Object> copy = Lists.newArrayList(collection);
// Process the collection before we pass it on
for (Object element : copy) {
processElement(element);
}
return delegate.addAll(copy);
}
@Override
public synchronized Object set(int index, Object element) {
Object old = delegate.set(index, element);
// Handle the old future, and the newly inserted future
if (old != element) {
unprocessElement(old);
processElement(element);
}
return old;
}
/**
* Process a single element.
* @param element - the element.
*/
protected void processElement(Object element) {
if (element instanceof ChannelFuture) {
processBootstrap((ChannelFuture) element);
}
}
/**
* Unprocess a single element.
* @param element - the element to unprocess.
*/
protected void unprocessElement(Object element) {
if (element instanceof ChannelFuture) {
unprocessBootstrap((ChannelFuture) element);
}
}
/**
* Process a single channel future.
* @param future - the future.
*/
protected void processBootstrap(ChannelFuture future) {
// Important: Must be addFirst()
future.channel().pipeline().addFirst(handler);
}
/**
* Revert any changes we made to the channel future.
* @param future - the future.
*/
protected void unprocessBootstrap(ChannelFuture future) {
final Channel channel = future.channel();
// For thread safety - see ChannelInjector.close()
channel.eventLoop().submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
channel.pipeline().remove(handler);
return null;
}
});
}
/**
* Close and revert all changes.
*/
public synchronized void close() {
for (Object element : this)
unprocessElement(element);
}
// Boiler plate
@Override
public synchronized int size() {
return delegate.size();
}
@Override
public synchronized boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean contains(Object o) {
return delegate.contains(o);
}
@Override
public synchronized Iterator<Object> iterator() {
return delegate.iterator();
}
@Override
public synchronized Object[] toArray() {
return delegate.toArray();
}
@Override
public synchronized <T> T[] toArray(T[] a) {
return delegate.toArray(a);
}
@Override
public synchronized boolean remove(Object o) {
return delegate.remove(o);
}
@Override
public synchronized boolean containsAll(Collection<?> c) {
return delegate.containsAll(c);
}
@Override
public synchronized boolean addAll(int index, Collection<? extends Object> c) {
return delegate.addAll(index, c);
}
@Override
public synchronized boolean removeAll(Collection<?> c) {
return delegate.removeAll(c);
}
@Override
public synchronized boolean retainAll(Collection<?> c) {
return delegate.retainAll(c);
}
@Override
public synchronized void clear() {
delegate.clear();
}
@Override
public synchronized Object get(int index) {
return delegate.get(index);
}
@Override
public synchronized void add(int index, Object element) {
delegate.add(index, element);
}
@Override
public synchronized Object remove(int index) {
return delegate.remove(index);
}
@Override
public synchronized int indexOf(Object o) {
return delegate.indexOf(o);
}
@Override
public synchronized int lastIndexOf(Object o) {
return delegate.lastIndexOf(o);
}
@Override
public synchronized ListIterator<Object> listIterator() {
return delegate.listIterator();
}
@Override
public synchronized ListIterator<Object> listIterator(int index) {
return delegate.listIterator(index);
}
@Override
public synchronized List<Object> subList(int fromIndex, int toIndex) {
return delegate.subList(fromIndex, toIndex);
}
// End boiler plate
}
@@ -1,938 +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 com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.NetworkProcessor;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.AttributeKey;
import io.netty.util.internal.TypeParameterMatcher;
import net.sf.cglib.proxy.Factory;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Represents a channel injector.
* @author Kristian
*/
public class ChannelInjector extends ByteToMessageDecoder implements Injector {
private static final ReportType REPORT_CANNOT_INTERCEPT_SERVER_PACKET = new ReportType("Unable to intercept a written server packet.");
private static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet.");
private static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread.");
private static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s");
/**
* Indicates that a packet has bypassed packet listeners.
*/
private static final PacketEvent BYPASSED_PACKET = new PacketEvent(ChannelInjector.class);
// The login packet
private static Class<?> PACKET_LOGIN_CLIENT = null;
private static FieldAccessor LOGIN_GAME_PROFILE = null;
// Versioning
private static Class<?> PACKET_SET_PROTOCOL = null;
private static AtomicInteger keyId = new AtomicInteger();
private static AttributeKey<Integer> PROTOCOL_KEY;
static {
PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL-" + keyId.getAndIncrement());
}
// Saved accessors
private static Method DECODE_BUFFER;
private static Method ENCODE_BUFFER;
private static FieldAccessor ENCODER_TYPE_MATCHER;
// For retrieving the protocol
private static FieldAccessor PROTOCOL_ACCESSOR;
// The factory that created this injector
private InjectionFactory factory;
// The player, or temporary player
private Player player;
private Player updated;
private String playerName;
// The player connection
private Object playerConnection;
// The current network manager and channel
private final Object networkManager;
private final Channel originalChannel;
private VolatileField channelField;
// Known network markers
private Map<Object, NetworkMarker> packetMarker = new WeakHashMap<>();
/**
* Indicate that this packet has been processed by event listeners.
* <p>
* This must never be set outside the channel pipeline's thread.
*/
private PacketEvent currentEvent;
/**
* A packet event that should be processed by the write method.
*/
private PacketEvent finalEvent;
/**
* A flag set by the main thread to indiciate that a packet should not be processed.
*/
private final ThreadLocal<Boolean> scheduleProcessPackets = ThreadLocal.withInitial(() -> true);
// Other handlers
private ByteToMessageDecoder vanillaDecoder;
private MessageToByteEncoder<Object> vanillaEncoder;
private Deque<PacketEvent> finishQueue = new ArrayDeque<>();
// The channel listener
private ChannelListener channelListener;
// Processing network markers
private NetworkProcessor processor;
// Closed
private boolean injected;
private boolean closed;
/**
* Construct a new channel injector.
* @param player - the current player, or temporary player.
* @param networkManager - its network manager.
* @param channel - its channel.
* @param channelListener - a listener.
* @param factory - the factory that created this injector
*/
ChannelInjector(Player player, Object networkManager, Channel channel, ChannelListener channelListener, InjectionFactory factory) {
this.player = Preconditions.checkNotNull(player, "player cannot be NULL");
this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL");
this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL");
this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL");
this.factory = Preconditions.checkNotNull(factory, "factory cannot be NULL");
this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter());
// Get the channel field
this.channelField = new VolatileField(FuzzyReflection
.fromObject(networkManager, true)
.getFieldByType("channel", Channel.class), networkManager, true);
}
/**
* Get the version of the current protocol.
* @return The version.
*/
@Override
public int getProtocolVersion() {
Integer value = originalChannel.attr(PROTOCOL_KEY).get();
return value != null ? value : MinecraftProtocolVersion.getCurrentVersion();
}
@Override
@SuppressWarnings("unchecked")
public boolean inject() {
synchronized (networkManager) {
if (closed)
return false;
if (originalChannel instanceof Factory)
return false;
if (!originalChannel.isActive())
return false;
// Main thread? We should synchronize with the channel thread, otherwise we might see a
// pipeline with only some of the handlers removed
if (Bukkit.isPrimaryThread()) {
// Just like in the close() method, we'll avoid blocking the main thread
executeInChannelThread(this::inject);
return false; // We don't know
}
// Don't inject the same channel twice
if (findChannelHandler(originalChannel, ChannelInjector.class) != null) {
return false;
}
// Get the vanilla decoder, so we don't have to replicate the work
vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder");
vanillaEncoder = (MessageToByteEncoder<Object>) originalChannel.pipeline().get("encoder");
if (vanillaDecoder == null)
throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline());
if (vanillaEncoder == null)
throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline());
patchEncoder(vanillaEncoder);
if (DECODE_BUFFER == null) {
try {
DECODE_BUFFER = vanillaDecoder.getClass().getDeclaredMethod("decode",
ChannelHandlerContext.class, ByteBuf.class, List.class);
DECODE_BUFFER.setAccessible(true);
} catch (NoSuchMethodException ex) {
throw new IllegalArgumentException("Unable to find decode method in " + vanillaDecoder.getClass());
}
}
if (ENCODE_BUFFER == null) {
try {
ENCODE_BUFFER = vanillaEncoder.getClass().getDeclaredMethod("encode",
ChannelHandlerContext.class, Object.class, ByteBuf.class);
ENCODE_BUFFER.setAccessible(true);
} catch (NoSuchMethodException ex) {
throw new IllegalArgumentException("Unable to find encode method in " + vanillaEncoder.getClass());
}
}
// Intercept sent packets
MessageToByteEncoder<Object> protocolEncoder = new MessageToByteEncoder<Object>() {
@Override
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
if (packet instanceof WirePacket) {
// Special case for wire format
ChannelInjector.this.encodeWirePacket((WirePacket) packet, output);
} else {
ChannelInjector.this.encode(ctx, packet, output);
}
}
@Override
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
super.write(ctx, packet, promise);
ChannelInjector.this.finalWrite();
}
};
// Intercept recieved packets
ChannelInboundHandlerAdapter finishHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// Execute context first
ctx.fireChannelRead(msg);
ChannelInjector.this.finishRead();
}
};
// Insert our handlers - note that we effectively replace the vanilla encoder/decoder
originalChannel.pipeline().addBefore("decoder", "protocol_lib_decoder", this);
originalChannel.pipeline().addBefore("protocol_lib_decoder", "protocol_lib_finish", finishHandler);
originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder);
// Intercept all write methods
channelField.setValue(new ChannelProxy(originalChannel, MinecraftReflection.getPacketClass()) {
// Compatibility with Spigot 1.8
private final PipelineProxy pipelineProxy = new PipelineProxy(originalChannel.pipeline(), this) {
@Override
public ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler) {
// Correct the position of the decoder
if ("decoder".equals(baseName)) {
if (super.get("protocol_lib_decoder") != null && guessCompression(handler)) {
super.addBefore("protocol_lib_decoder", name, handler);
return this;
}
}
return super.addBefore(baseName, name, handler);
}
};
@Override
public ChannelPipeline pipeline() {
return pipelineProxy;
}
@Override
protected <T> Callable<T> onMessageScheduled(final Callable<T> callable, FieldAccessor packetAccessor) {
final PacketEvent event = handleScheduled(callable, packetAccessor);
// Handle cancelled events
if (event != null && event.isCancelled())
return null;
return () -> {
T result;
// This field must only be updated in the pipeline thread
currentEvent = event;
result = callable.call();
currentEvent = null;
return result;
};
}
@Override
protected Runnable onMessageScheduled(final Runnable runnable, FieldAccessor packetAccessor) {
final PacketEvent event = handleScheduled(runnable, packetAccessor);
// Handle cancelled events
if (event != null && event.isCancelled())
return null;
return () -> {
currentEvent = event;
runnable.run();
currentEvent = null;
};
}
PacketEvent handleScheduled(Object instance, FieldAccessor accessor) {
// Let the filters handle this packet
Object original = accessor.get(instance);
// See if we've been instructed not to process packets
if (!scheduleProcessPackets.get()) {
NetworkMarker marker = getMarker(original);
if (marker != null) {
PacketEvent result = new PacketEvent(ChannelInjector.class);
result.setNetworkMarker(marker);
return result;
} else {
return BYPASSED_PACKET;
}
}
PacketEvent event = processSending(original);
if (event != null && !event.isCancelled()) {
Object changed = event.getPacket().getHandle();
// Change packet to be scheduled
if (original != changed) {
accessor.set(instance, changed);
}
}
return event != null ? event : BYPASSED_PACKET;
}
});
injected = true;
return true;
}
}
/**
* Determine if the given object is a compressor or decompressor.
* @param handler - object to test.
* @return TRUE if it is, FALSE if not or unknown.
*/
private boolean guessCompression(ChannelHandler handler) {
String className = handler != null ? handler.getClass().getCanonicalName() : "";
return className.contains("Compressor") || className.contains("Decompressor");
}
/**
* Process a given message on the packet listeners.
* @param message - the message/packet.
* @return The resulting message/packet.
*/
private PacketEvent processSending(Object message) {
return channelListener.onPacketSending(ChannelInjector.this, message, getMarker(message));
}
/**
* This method patches the encoder so that it skips already created packets.
* @param encoder - the encoder to patch.
*/
private void patchEncoder(MessageToByteEncoder<Object> encoder) {
if (ENCODER_TYPE_MATCHER == null) {
ENCODER_TYPE_MATCHER = Accessors.getFieldAccessor(encoder.getClass(), "matcher", true);
}
ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass()));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (channelListener.isDebug()) {
cause.printStackTrace();
}
super.exceptionCaught(ctx, cause);
}
private void encodeWirePacket(WirePacket packet, ByteBuf output) {
packet.writeId(output);
packet.writeBytes(output);
}
/**
* Encode a packet to a byte buffer, taking over for the standard Minecraft encoder.
* @param ctx - the current context.
* @param packet - the packet to encode to a byte array.
* @param output - the output byte array.
*/
private void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
NetworkMarker marker = null;
PacketEvent event = currentEvent;
try {
// Skip every kind of non-filtered packet
if (!scheduleProcessPackets.get()) {
return;
}
// This packet has not been seen by the main thread
if (event == null) {
Class<?> clazz = packet.getClass();
// Schedule the transmission on the main thread instead
if (channelListener.hasMainThreadListener(clazz)) {
// Delay the packet
scheduleMainThread(packet);
packet = null;
} else {
event = processSending(packet);
// Handle the output
if (event != null) {
packet = !event.isCancelled() ? event.getPacket().getHandle() : null;
}
}
}
if (event != null) {
// Retrieve marker without accidentally constructing it
marker = NetworkMarker.getNetworkMarker(event);
}
// Process output handler
if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) {
ByteBuf packetBuffer = ctx.alloc().buffer();
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, packetBuffer);
// Let each handler prepare the actual output
byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer));
// Write the result
output.writeBytes(data);
packet = null;
// Sent listeners?
finalEvent = event;
}
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof Exception) {
throw (Exception) ex.getCause();
}
} catch (Exception e) {
channelListener.getReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_INTERCEPT_SERVER_PACKET).callerParam(packet).error(e).build());
} finally {
// Attempt to handle the packet nevertheless
if (packet != null) {
try {
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, output);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof Exception) {
//noinspection ThrowFromFinallyBlock
throw (Exception) ex.getCause();
}
}
finalEvent = event;
}
}
}
/**
* Invoked when a packet has been written to the channel
*/
private void finalWrite() {
PacketEvent event = finalEvent;
if (event != null) {
// Necessary to prevent infinite loops
finalEvent = null;
currentEvent = null;
processor.invokePostEvent(event, NetworkMarker.getNetworkMarker(event));
}
}
private void scheduleMainThread(final Object packetCopy) {
// Don't use BukkitExecutors for this - it has a bit of overhead
Bukkit.getScheduler().scheduleSyncDelayedTask(factory.getPlugin(), () -> {
if (!closed) {
invokeSendPacket(packetCopy);
}
});
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception {
try {
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
// Reset queue
finishQueue.clear();
for (ListIterator<Object> it = packets.listIterator(); it.hasNext(); ) {
Object input = it.next();
Class<?> packetClass = input.getClass();
NetworkMarker marker = null;
// Special case!
handleLogin(packetClass, input);
if (channelListener.includeBuffer(packetClass)) {
if (byteBuffer.readableBytes() != 0) {
byteBuffer.resetReaderIndex();
marker = new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer));
}
}
PacketEvent output = channelListener.onPacketReceiving(this, input, marker);
// Handle packet changes
if (output != null) {
if (output.isCancelled()) {
it.remove();
} else {
if (output.getPacket().getHandle() != input) {
it.set(output.getPacket().getHandle());
}
finishQueue.addLast(output);
}
}
}
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof Exception) {
throw (Exception) ex.getCause();
}
} catch (Exception e) {
channelListener.getReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build());
}
}
/**
* Invoked after our decoder
*/
private void finishRead() {
// Assume same order
PacketEvent event = finishQueue.pollFirst();
if (event != null) {
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
if (marker != null) {
processor.invokePostEvent(event, marker);
}
}
}
/**
* Invoked when we may need to handle the login packet.
* @param packetClass - the packet class.
* @param packet - the packet.
*/
private void handleLogin(Class<?> packetClass, Object packet) {
// Try to find the login packet class
if (PACKET_LOGIN_CLIENT == null) {
PACKET_LOGIN_CLIENT = PacketType.Login.Client.START.getPacketClass();
}
// If we can't, there's an issue
if (PACKET_LOGIN_CLIENT == null) {
throw new IllegalStateException("Failed to obtain login start packet. Did you build Spigot with BuildTools?");
}
if (LOGIN_GAME_PROFILE == null) {
LOGIN_GAME_PROFILE = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, MinecraftReflection.getGameProfileClass(), true);
}
// See if we are dealing with the login packet
if (PACKET_LOGIN_CLIENT.equals(packetClass)) {
WrappedGameProfile profile = WrappedGameProfile.fromHandle(LOGIN_GAME_PROFILE.get(packet));
// Save the channel injector
factory.cacheInjector(profile.getName(), this);
}
if (PACKET_SET_PROTOCOL == null) {
try {
PACKET_SET_PROTOCOL = PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass();
} catch (Throwable ex) {
PACKET_SET_PROTOCOL = getClass(); // If we can't find it don't worry about it
}
}
if (PACKET_SET_PROTOCOL.equals(packetClass)) {
FuzzyReflection fuzzy = FuzzyReflection.fromObject(packet);
try {
int protocol = (int) fuzzy.invokeMethod(packet, "getProtocol", int.class);
originalChannel.attr(PROTOCOL_KEY).set(protocol);
} catch (Throwable ex) {
// Oh well
}
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
// See NetworkManager.channelActive(ChannelHandlerContext) for why
if (channelField != null) {
channelField.refreshValue();
}
}
/**
* Retrieve every byte in the given byte buffer.
* @param buffer - the buffer.
* @return The bytes.
*/
private byte[] getBytes(ByteBuf buffer) {
byte[] data = new byte[buffer.readableBytes()];
buffer.readBytes(data);
return data;
}
/**
* Disconnect the current player.
* @param message - the disconnect message, if possible.
*/
private void disconnect(String message) {
// If we're logging in, we can only close the channel
if (playerConnection == null || player instanceof Factory) {
originalChannel.disconnect();
} else {
// Call the disconnect method
try {
MinecraftMethods.getDisconnectMethod(playerConnection.getClass()).
invoke(playerConnection, message);
} catch (Exception e) {
throw new IllegalArgumentException("Unable to invoke disconnect method.", e);
}
}
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
saveMarker(packet, marker);
try {
scheduleProcessPackets.set(filtered);
invokeSendPacket(packet);
} finally {
scheduleProcessPackets.set(true);
}
}
/**
* Invoke the sendPacket method in Minecraft.
* @param packet - the packet to send.
*/
private void invokeSendPacket(Object packet) {
Validate.isTrue(!closed, "cannot send packets to a closed channel");
// Attempt to send the packet with NetworkMarker.handle(), or the PlayerConnection if its active
try {
if (player instanceof Factory) {
MinecraftMethods.getNetworkManagerHandleMethod().invoke(networkManager, packet);
} else {
MinecraftMethods.getSendPacketMethod().invoke(getPlayerConnection(), packet);
}
} catch (Throwable ex) {
ProtocolLibrary.getErrorReporter().reportWarning(this,
Report.newBuilder(REPORT_CANNOT_SEND_PACKET).messageParam(packet, playerName).error(ex).build());
}
}
@Override
public void recieveClientPacket(final Object packet) {
// TODO: Ensure the packet listeners are executed in the channel thread.
// Execute this in the channel thread
Runnable action = () -> {
try {
MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(networkManager, null, packet);
} catch (Exception e) {
// Inform the user
ProtocolLibrary.getErrorReporter().reportMinimal(factory.getPlugin(), "recieveClientPacket", e);
}
};
// Execute in the worker thread
if (originalChannel.eventLoop().inEventLoop()) {
action.run();
} else {
originalChannel.eventLoop().execute(action);
}
}
@Override
public Protocol getCurrentProtocol() {
if (PROTOCOL_ACCESSOR == null) {
PROTOCOL_ACCESSOR = Accessors.getFieldAccessor(
networkManager.getClass(), MinecraftReflection.getEnumProtocolClass(), true);
}
return Protocol.fromVanilla((Enum<?>) PROTOCOL_ACCESSOR.get(networkManager));
}
/**
* Retrieve the player connection of the current player.
* @return The player connection.
*/
private Object getPlayerConnection() {
if (playerConnection == null) {
Player player = getPlayer();
if (player == null) {
throw new IllegalStateException("cannot send packet to offline player" + (playerName != null ? " " + playerName : ""));
}
playerConnection = MinecraftFields.getPlayerConnection(player);
}
return playerConnection;
}
@Override
public NetworkMarker getMarker(Object packet) {
return packetMarker.get(packet);
}
@Override
public void saveMarker(Object packet, NetworkMarker marker) {
if (marker != null) {
packetMarker.put(packet, marker);
}
}
@Override
public Player getPlayer() {
if (player == null && playerName != null) {
return Bukkit.getPlayerExact(playerName);
}
return player;
}
/**
* Set the player instance.
* @param player - current instance.
*/
@Override
public void setPlayer(Player player) {
this.player = player;
this.playerName = player.getName();
}
/**
* Set the updated player instance.
* @param updated - updated instance.
*/
@Override
public void setUpdatedPlayer(Player updated) {
this.updated = updated;
this.playerName = updated.getName();
}
@Override
public boolean isInjected() {
return injected;
}
/**
* Determine if this channel has been closed and cleaned up.
* @return TRUE if it has, FALSE otherwise.
*/
@Override
public boolean isClosed() {
return closed;
}
@Override
public void close() {
if (!closed) {
closed = true;
if (injected) {
channelField.revertValue();
// Calling remove() in the main thread will block the main thread, which may lead
// to a deadlock:
// http://pastebin.com/L3SBVKzp
//
// ProtocolLib executes this close() method through a PlayerQuitEvent in the main thread,
// which has implicitly aquired a lock on SimplePluginManager (see SimplePluginManager.callEvent(Event)).
// Unfortunately, the remove() method will schedule the removal on one of the Netty worker threads if
// it's called from a different thread, blocking until the removal has been confirmed.
//
// This is bad enough (Rule #1: Don't block the main thread), but the real trouble starts if the same
// worker thread happens to be handling a server ping connection when this removal task is scheduled.
// In that case, it may attempt to invoke an asynchronous ServerPingEvent (see PacketStatusListener)
// using SimplePluginManager.callEvent(). But, since this has already been locked by the main thread,
// we end up with a deadlock. The main thread is waiting for the worker thread to process the task, and
// the worker thread is waiting for the main thread to finish executing PlayerQuitEvent.
//
// TL;DR: Concurrency is hard.
executeInChannelThread(() -> {
String[] handlers = new String[] {
"protocol_lib_decoder", "protocol_lib_finish", "protocol_lib_encoder"
};
for (String handler : handlers) {
try {
originalChannel.pipeline().remove(handler);
} catch (NoSuchElementException e) {
// Ignore
}
}
});
// Clear cache
factory.invalidate(player);
// Clear player instances
// Should help fix memory leaks
this.player = null;
this.updated = null;
}
}
}
/**
* Execute a specific command in the channel thread.
* <p>
* Exceptions are printed through the standard error reporter mechanism.
* @param command - the command to execute.
*/
private void executeInChannelThread(final Runnable command) {
originalChannel.eventLoop().execute(() -> {
try {
command.run();
} catch (Exception e) {
ProtocolLibrary.getErrorReporter().reportDetailed(ChannelInjector.this,
Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build());
}
});
}
/**
* Find the first channel handler that is assignable to a given type.
* @param channel - the channel.
* @param clazz - the type.
* @return The first handler, or NULL.
*/
static ChannelHandler findChannelHandler(Channel channel, Class<?> clazz) {
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
if (clazz.isAssignableFrom(entry.getValue().getClass())) {
return entry.getValue();
}
}
return null;
}
/**
* Represents a socket injector that foreards to the current channel injector.
* @author Kristian
*/
public static class ChannelSocketInjector implements SocketInjector {
private final ChannelInjector injector;
ChannelSocketInjector(ChannelInjector injector) {
this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL");
}
@Override
public Socket getSocket() {
return SocketAdapter.adapt((SocketChannel) injector.originalChannel);
}
@Override
public SocketAddress getAddress() {
return injector.originalChannel.remoteAddress();
}
@Override
public void disconnect(String message) {
injector.disconnect(message);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
injector.sendServerPacket(packet, marker, filtered);
}
@Override
public Player getPlayer() {
return injector.getPlayer();
}
@Override
public Player getUpdatedPlayer() {
return injector.updated != null ? injector.updated : getPlayer();
}
@Override
public void transferState(SocketInjector delegate) {
// Do nothing
}
@Override
public void setUpdatedPlayer(Player updatedPlayer) {
injector.setPlayer(updatedPlayer);
}
ChannelInjector getChannelInjector() {
return injector;
}
}
public Channel getChannel() {
return originalChannel;
}
}
@@ -1,66 +1,70 @@
package com.comphenix.protocol.injector.netty;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
/**
* Represents a listener for received or sent packets.
*
* @author Kristian
*/
public interface ChannelListener {
/**
* Invoked when a packet is being sent to the client.
* <p>
* This is invoked on the main thread.
*
* @param injector - the channel injector.
* @param packet - the packet.
* @param marker - the network marker.
* @param packet - the packet.
* @param marker - the network marker.
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
*/
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker);
PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker);
/**
* Invoked when a packet is being received from a client.
* <p>
* This is invoked on an asynchronous worker thread.
*
* @param injector - the channel injector.
* @param packet - the packet.
* @param marker - the associated network marker, if any.
* @param packet - the packet.
* @param marker - the associated network marker, if any.
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
*/
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker);
PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker);
/**
* Determine if there is a packet listener for the given packet.
*
* @param packetClass - the packet class to check.
* @return TRUE if there is such a listener, FALSE otherwise.
*/
public boolean hasListener(Class<?> packetClass);
boolean hasListener(Class<?> packetClass);
/**
* Determine if there is a server packet listener that must be executed on the main thread.
*
* @param packetClass - the packet class to check.
* @return TRUE if there is, FALSE otherwise.
*/
public boolean hasMainThreadListener(Class<?> packetClass);
/**
* Determine if we need the buffer data of a given client side packet.
* @param packetClass - the packet class.
* @return TRUE if we do, FALSE otherwise.
*/
public boolean includeBuffer(Class<?> packetClass);
boolean hasMainThreadListener(Class<?> packetClass);
boolean hasMainThreadListener(PacketType type);
/**
* Retrieve the current error reporter.
*
* @return The error reporter.
*/
public ErrorReporter getReporter();
/**
* Determine if debug mode is enabled.
* @return TRUE if it is, FALSE otherwise.
*/
ErrorReporter getReporter();
/**
* Determine if debug mode is enabled.
*
* @return TRUE if it is, FALSE otherwise.
*/
boolean isDebug();
}
@@ -1,352 +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.lang.reflect.Field;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.Callable;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.google.common.collect.Maps;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelProgressivePromise;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
public abstract class ChannelProxy implements Channel {
// Mark that a certain object does not contain a message field
private static final FieldAccessor MARK_NO_MESSAGE = new FieldAccessor() {
@Override
public void set(Object instance, Object value) { }
@Override
public Object get(Object instance) { return null; }
@Override
public Field getField() { return null; };
};
// Looking up packets in inner classes
private static Map<Class<?>, FieldAccessor> MESSAGE_LOOKUP = Maps.newConcurrentMap();
// The underlying channel
protected Channel delegate;
protected Class<?> messageClass;
// Event loop proxy
private transient EventLoopProxy loopProxy;
public ChannelProxy(Channel delegate, Class<?> messageClass) {
this.delegate = delegate;
this.messageClass = messageClass;
}
/**
* Invoked when a packet is scheduled for transmission in the event loop.
* @param <T> Type
* @param callable - callable to schedule for execution.
* @param packetAccessor - accessor for modifying the packet in the callable.
* @return The callable that will be scheduled, or NULL to cancel.
*/
protected abstract <T> Callable<T> onMessageScheduled(Callable<T> callable, FieldAccessor packetAccessor);
/**
* Invoked when a packet is scheduled for transmission in the event loop.
* @param runnable - the runnable that contains a packet to be scheduled.
* @param packetAccessor - accessor for modifying the packet in the runnable.
* @return The runnable that will be scheduled, or NULL to cancel.
*/
protected abstract Runnable onMessageScheduled(Runnable runnable, FieldAccessor packetAccessor);
@Override
public <T> Attribute<T> attr(AttributeKey<T> paramAttributeKey) {
return delegate.attr(paramAttributeKey);
}
@Override
public ChannelFuture bind(SocketAddress paramSocketAddress) {
return delegate.bind(paramSocketAddress);
}
@Override
public ChannelPipeline pipeline() {
return delegate.pipeline();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress) {
return delegate.connect(paramSocketAddress);
}
@Override
public ByteBufAllocator alloc() {
return delegate.alloc();
}
@Override
public ChannelPromise newPromise() {
return delegate.newPromise();
}
@Override
public EventLoop eventLoop() {
if (loopProxy == null) {
loopProxy = new EventLoopProxy() {
@Override
protected EventLoop getDelegate() {
return delegate.eventLoop();
}
@Override
protected Runnable schedulingRunnable(final Runnable runnable) {
final FieldAccessor accessor = getMessageAccessor(runnable);
if (accessor != null) {
Runnable result = onMessageScheduled(runnable, accessor);;
return result != null ? result : getEmptyRunnable();
}
return runnable;
}
@Override
protected <T> Callable<T> schedulingCallable(Callable<T> callable) {
FieldAccessor accessor = getMessageAccessor(callable);
if (accessor != null) {
Callable<T> result = onMessageScheduled(callable, accessor);;
return result != null ? result : EventLoopProxy.<T>getEmptyCallable();
}
return callable;
}
};
}
return loopProxy;
}
/**
* Retrieve a way to access the packet field of an object.
* @param value - the object.
* @return The packet field accessor, or NULL if not found.
*/
private FieldAccessor getMessageAccessor(Object value) {
Class<?> clazz = value.getClass();
FieldAccessor accessor = MESSAGE_LOOKUP.get(clazz);
if (accessor == null) {
try {
accessor = Accessors.getFieldAccessor(clazz, messageClass, true);
} catch (IllegalArgumentException e) {
accessor = MARK_NO_MESSAGE;
}
// Save the result
MESSAGE_LOOKUP.put(clazz, accessor);
}
return accessor != MARK_NO_MESSAGE ? accessor : null;
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress1,
SocketAddress paramSocketAddress2) {
return delegate.connect(paramSocketAddress1, paramSocketAddress2);
}
@Override
public ChannelProgressivePromise newProgressivePromise() {
return delegate.newProgressivePromise();
}
@Override
public Channel parent() {
return delegate.parent();
}
@Override
public ChannelConfig config() {
return delegate.config();
}
@Override
public ChannelFuture newSucceededFuture() {
return delegate.newSucceededFuture();
}
@Override
public boolean isOpen() {
return delegate.isOpen();
}
@Override
public ChannelFuture disconnect() {
return delegate.disconnect();
}
@Override
public boolean isRegistered() {
return delegate.isRegistered();
}
@Override
public ChannelFuture newFailedFuture(Throwable paramThrowable) {
return delegate.newFailedFuture(paramThrowable);
}
@Override
public ChannelFuture close() {
return delegate.close();
}
@Override
public boolean isActive() {
return delegate.isActive();
}
@Override
@Deprecated
public ChannelFuture deregister() {
return delegate.deregister();
}
@Override
public ChannelPromise voidPromise() {
return delegate.voidPromise();
}
@Override
public ChannelMetadata metadata() {
return delegate.metadata();
}
@Override
public ChannelFuture bind(SocketAddress paramSocketAddress,
ChannelPromise paramChannelPromise) {
return delegate.bind(paramSocketAddress, paramChannelPromise);
}
@Override
public SocketAddress localAddress() {
return delegate.localAddress();
}
@Override
public SocketAddress remoteAddress() {
return delegate.remoteAddress();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress,
ChannelPromise paramChannelPromise) {
return delegate.connect(paramSocketAddress, paramChannelPromise);
}
@Override
public ChannelFuture closeFuture() {
return delegate.closeFuture();
}
@Override
public boolean isWritable() {
return delegate.isWritable();
}
@Override
public Channel flush() {
return delegate.flush();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress1,
SocketAddress paramSocketAddress2, ChannelPromise paramChannelPromise) {
return delegate.connect(paramSocketAddress1, paramSocketAddress2, paramChannelPromise);
}
@Override
public Channel read() {
return delegate.read();
}
@Override
public Unsafe unsafe() {
return delegate.unsafe();
}
@Override
public ChannelFuture disconnect(ChannelPromise paramChannelPromise) {
return delegate.disconnect(paramChannelPromise);
}
@Override
public ChannelFuture close(ChannelPromise paramChannelPromise) {
return delegate.close(paramChannelPromise);
}
@Override
@Deprecated
public ChannelFuture deregister(ChannelPromise paramChannelPromise) {
return delegate.deregister(paramChannelPromise);
}
@Override
public ChannelFuture write(Object paramObject) {
return delegate.write(paramObject);
}
@Override
public ChannelFuture write(Object paramObject, ChannelPromise paramChannelPromise) {
return delegate.write(paramObject, paramChannelPromise);
}
@Override
public ChannelFuture writeAndFlush(Object paramObject, ChannelPromise paramChannelPromise) {
return delegate.writeAndFlush(paramObject, paramChannelPromise);
}
@Override
public ChannelFuture writeAndFlush(Object paramObject) {
return delegate.writeAndFlush(paramObject);
}
@Override
public int compareTo(Channel o) {
return delegate.compareTo(o);
}
/* Added in Netty 4.1, seem to be unused
public long bytesBeforeUnwritable() {
return delegate.bytesBeforeUnwritable();
}
public long bytesBeforeWritable() {
return delegate.bytesBeforeWritable();
}
public ChannelId id() {
return delegate.id();
}
public <T> boolean hasAttr(AttributeKey<T> key) {
return delegate.hasAttr(key);
}
*/
}
@@ -1,265 +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 io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ProgressivePromise;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* An event loop proxy.
* @author Kristian.
*/
abstract class EventLoopProxy implements EventLoop {
private static final Runnable EMPTY_RUNNABLE = new Runnable() {
@Override
public void run() {
// Do nothing
}
};
private static final Callable<?> EMPTY_CALLABLE = new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
};
};
/**
* Retrieve the underlying event loop.
* @return The event loop.
*/
protected abstract EventLoop getDelegate();
/**
* Retrieve a callable that does nothing but return NULL.
* @return The empty callable.
*/
@SuppressWarnings("unchecked")
public static <T> Callable<T> getEmptyCallable() {
return (Callable<T>) EMPTY_CALLABLE;
}
/**
* Retrieve a runnable that does nothing.
* @return A NO-OP runnable.
*/
public static Runnable getEmptyRunnable() {
return EMPTY_RUNNABLE;
}
/**
* Invoked when a runnable is being scheduled.
* @param runnable - the runnable that is scheduling.
* @return The runnable to schedule instead. Cannot be NULL.
*/
protected abstract Runnable schedulingRunnable(Runnable runnable);
/**
* Invoked when a callable is being scheduled.
* @param runnable - the callable that is scheduling.
* @return The callable to schedule instead. Cannot be NULL.
*/
protected abstract <T> Callable<T> schedulingCallable(Callable<T> callable);
@Override
public void execute(Runnable command) {
getDelegate().execute(schedulingRunnable(command));
}
@Override
public <T> Future<T> submit(Callable<T> action) {
return getDelegate().submit(schedulingCallable(action));
}
@Override
public <T> Future<T> submit(Runnable action, T arg1) {
return getDelegate().submit(schedulingRunnable(action), arg1);
}
@Override
public Future<?> submit(Runnable action) {
return getDelegate().submit(schedulingRunnable(action));
}
@Override
public <V> ScheduledFuture<V> schedule(Callable<V> action, long arg1, TimeUnit arg2) {
return getDelegate().schedule(schedulingCallable(action), arg1, arg2);
}
@Override
public ScheduledFuture<?> schedule(Runnable action, long arg1, TimeUnit arg2) {
return getDelegate().schedule(schedulingRunnable(action), arg1, arg2);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable action, long arg1, long arg2, TimeUnit arg3) {
return getDelegate().scheduleAtFixedRate(schedulingRunnable(action), arg1, arg2, arg3);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable action, long arg1, long arg2, TimeUnit arg3) {
return getDelegate().scheduleWithFixedDelay(schedulingRunnable(action), arg1, arg2, arg3);
}
// Boiler plate:
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return getDelegate().awaitTermination(timeout, unit);
}
@Override
public boolean inEventLoop() {
return getDelegate().inEventLoop();
}
@Override
public boolean inEventLoop(Thread arg0) {
return getDelegate().inEventLoop(arg0);
}
@Override
public boolean isShutdown() {
return getDelegate().isShutdown();
}
@Override
public boolean isTerminated() {
return getDelegate().isTerminated();
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
return getDelegate().invokeAll(tasks);
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
TimeUnit unit) throws InterruptedException {
return getDelegate().invokeAll(tasks, timeout, unit);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException,
ExecutionException {
return getDelegate().invokeAny(tasks);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return getDelegate().invokeAny(tasks, timeout, unit);
}
@Override
public boolean isShuttingDown() {
return getDelegate().isShuttingDown();
}
@Override
public Iterator<EventExecutor> iterator() {
return getDelegate().iterator();
}
@Override
public <V> Future<V> newFailedFuture(Throwable arg0) {
return getDelegate().newFailedFuture(arg0);
}
@Override
public EventLoop next() {
return ((EventLoopGroup) getDelegate()).next();
}
@Override
public <V> ProgressivePromise<V> newProgressivePromise() {
return getDelegate().newProgressivePromise();
}
@Override
public <V> Promise<V> newPromise() {
return getDelegate().newPromise();
}
@Override
public <V> Future<V> newSucceededFuture(V arg0) {
return getDelegate().newSucceededFuture(arg0);
}
@Override
public EventLoopGroup parent() {
return getDelegate().parent();
}
@Override
public ChannelFuture register(Channel arg0, ChannelPromise arg1) {
return getDelegate().register(arg0, arg1);
}
@Override
public ChannelFuture register(Channel arg0) {
return getDelegate().register(arg0);
}
@Override
public Future<?> shutdownGracefully() {
return getDelegate().shutdownGracefully();
}
@Override
public Future<?> shutdownGracefully(long arg0, long arg1, TimeUnit arg2) {
return getDelegate().shutdownGracefully(arg0, arg1, arg2);
}
@Override
public Future<?> terminationFuture() {
return getDelegate().terminationFuture();
}
@Override
@Deprecated
public void shutdown() {
getDelegate().shutdown();
}
@Override
@Deprecated
public List<Runnable> shutdownNow() {
return getDelegate().shutdownNow();
}
/*
public ChannelFuture register(ChannelPromise promise) {
return getDelegate().register(promise);
}
*/
}
@@ -1,236 +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 io.netty.channel.Channel;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.netty.ChannelInjector.ChannelSocketInjector;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
/**
* Represents an injector factory.
* <p>
* Note that the factory will return {@link ClosedInjector} when the factory is closed.
* @author Kristian
*/
public class InjectionFactory {
// This should work as long as the injectors are, uh, injected
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
// Whether or not the factory is closed
private volatile boolean closed;
// The current plugin
private final Plugin plugin;
public InjectionFactory(Plugin plugin) {
this.plugin = plugin;
}
/**
* Retrieve the main plugin associated with this injection factory.
* @return The main plugin.
*/
public Plugin getPlugin() {
return plugin;
}
/**
* Construct or retrieve a channel injector from an existing Bukkit player.
* @param player - the existing Bukkit player.
* @param listener - the listener.
* @return A new injector, an existing injector associated with this player, or a closed injector.
*/
@Nonnull
public Injector fromPlayer(Player player, ChannelListener listener) {
if (closed)
return new ClosedInjector(player);
Injector injector = playerLookup.get(player);
// Find a temporary injector as well
if (injector == null)
injector = getTemporaryInjector(player);
if (injector != null && !injector.isClosed())
return injector;
Object networkManager = MinecraftFields.getNetworkManager(player);
// Must be a temporary Bukkit player
if (networkManager == null) {
return fromName(player.getName(), player);
}
Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true);
// See if a channel has already been created
injector = (ChannelInjector) ChannelInjector.findChannelHandler(channel, ChannelInjector.class);
if (injector != null) {
// Update the player instance
playerLookup.remove(injector.getPlayer());
injector.setPlayer(player);
} else {
injector = new ChannelInjector(player, networkManager, channel, listener, this);
}
// Cache injector and return
cacheInjector(player, injector);
return injector;
}
/**
* Retrieve a cached injector from a name.
* <p>
* The injector may be NULL if the plugin has been reloaded during a player login.
* @param name - the name.
* @param player - the player.
* @return The cached injector, or a closed injector if it could not be found.
*/
public Injector fromName(String name, Player player) {
if (!closed) {
Injector injector = nameLookup.get(name);
// We can only retrieve cached injectors
if (injector != null) {
// Update instance
injector.setUpdatedPlayer(player);
return injector;
}
}
return new ClosedInjector(player);
}
/**
* Construct a new channel injector for the given channel.
* @param channel - the channel.
* @param listener - the listener.
* @param playerFactory - a temporary player creator.
* @return The channel injector, or a closed injector.
*/
@Nonnull
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
if (closed)
return new ClosedInjector(null);
Object networkManager = findNetworkManager(channel);
Player temporaryPlayer = playerFactory.createTemporaryPlayer(Bukkit.getServer());
ChannelInjector injector = new ChannelInjector(temporaryPlayer, networkManager, channel, listener, this);
// Initialize temporary player
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, new ChannelSocketInjector(injector));
return injector;
}
/**
* Invalidate a cached injector.
* @param player - the associated player.
* @return The cached injector, or NULL if nothing was cached.
*/
public Injector invalidate(Player player) {
Injector injector = playerLookup.remove(player);
nameLookup.remove(player.getName());
return injector;
}
/**
* Cache an injector by player.
* @param player - the player.
* @param injector - the injector to cache.
* @return The previously cached injector.
*/
public Injector cacheInjector(Player player, Injector injector) {
nameLookup.put(player.getName(), injector);
return playerLookup.put(player, injector);
}
/**
* Cache an injector by name alone.
* @param name - the name to lookup.
* @param injector - the injector.
* @return The cached injector.
*/
public Injector cacheInjector(String name, Injector injector) {
return nameLookup.put(name, injector);
}
/**
* Retrieve the associated channel injector.
* @param player - the temporary player, or normal Bukkit player.
* @return The associated injector, or NULL if this is a Bukkit player.
*/
private ChannelInjector getTemporaryInjector(Player player) {
SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector != null) {
return ((ChannelSocketInjector) injector).getChannelInjector();
}
return null;
}
/**
* Find the network manager in a channel's pipeline.
* @param channel - the channel.
* @return The network manager.
*/
private Object findNetworkManager(Channel channel) {
// Find the network manager
Object networkManager = ChannelInjector.findChannelHandler(channel, MinecraftReflection.getNetworkManagerClass());
if (networkManager != null)
return networkManager;
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
}
/**
* Determine if the factory is closed.
* <p>
* If it is, all new injectors will be closed by default.
* @return TRUE if it is closed, FALSE otherwise.
*/
public boolean isClosed() {
return closed;
}
/**
* Close all injectors created by this factory, and cease the creation of new injections.
*/
public synchronized void close() {
if (!closed) {
closed = true;
// Close everything
for (Injector injector : playerLookup.values())
injector.close();
for (Injector injector : nameLookup.values())
injector.close();
}
}
}
@@ -1,97 +1,100 @@
package com.comphenix.protocol.injector.netty;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.NetworkMarker;
import org.bukkit.entity.Player;
/**
* Represents an injected client connection.
*
* @author Kristian
*/
public interface Injector {
/**
* Retrieve the current protocol version of the player.
*
* @return Protocol version.
*/
public abstract int getProtocolVersion();
int getProtocolVersion();
/**
* Inject the current channel.
* <p>
* Note that only active channels can be injected.
*
* @return TRUE if we injected the channel, false if we could not inject or it was already injected.
*/
public abstract boolean inject();
boolean inject();
void uninject();
/**
* Close the current injector.
*/
public abstract void close();
void close();
/**
* Send a packet to a player's client.
* @param packet - the packet to send.
* @param marker - the network marker.
*
* @param packet - the packet to send.
* @param marker - the network marker.
* @param filtered - whether or not the packet is filtered.
*/
public abstract void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
/**
* Recieve a packet on the server.
* @param packet - the (NMS) packet to send.
*/
public abstract void recieveClientPacket(Object packet);
void receiveClientPacket(Object packet);
/**
* Retrieve the current protocol state.
*
* @return The current protocol.
*/
public abstract Protocol getCurrentProtocol();
Protocol getCurrentProtocol();
/**
* Retrieve the network marker associated with a given packet.
*
* @param packet - the packet.
* @return The network marker.
*/
public abstract NetworkMarker getMarker(Object packet);
NetworkMarker getMarker(Object packet);
/**
* Associate a given network marker with a specific packet.
*
* @param packet - the NMS packet.
* @param marker - the associated marker.
*/
public abstract void saveMarker(Object packet, NetworkMarker marker);
void saveMarker(Object packet, NetworkMarker marker);
/**
* Retrieve the current player or temporary player associated with the injector.
*
* @return The current player.
*/
public abstract Player getPlayer();
Player getPlayer();
/**
* Set the current player instance.
*
* @param player - the current player.
*/
public abstract void setPlayer(Player player);
void setPlayer(Player player);
void disconnect(String message);
/**
* Determine if the channel has already been injected.
*
* @return TRUE if it has, FALSE otherwise.
*/
public abstract boolean isInjected();
boolean isInjected();
/**
* Determine if this channel has been closed and cleaned up.
*
* @return TRUE if it has, FALSE otherwise.
*/
public abstract boolean isClosed();
/**
* Set the updated player instance.
* <p>
* This will not replace the current instance, but it will allow PacketEvent to provide additional player information.
* @param player - the more up-to-date player.
*/
public abstract void setUpdatedPlayer(Player player);
boolean isClosed();
}
@@ -1,25 +1,26 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2015 dmulloy2
* <p>
* 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.
* <p>
* 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.
* <p>
* 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 com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.io.ByteStreams;
import io.netty.buffer.AbstractByteBuf;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
@@ -33,35 +34,31 @@ import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.channels.WritableByteChannel;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.io.ByteStreams;
/**
* Construct a ByteBuf around an input stream and an output stream.
* <p>
* Note that as streams usually don't support seeking, this implementation will ignore
* all indexing in the byte buffer.
* Note that as streams usually don't support seeking, this implementation will ignore all indexing in the byte buffer.
*
* @author Kristian
*/
@SuppressWarnings("unused")
public class NettyByteBufAdapter extends AbstractByteBuf {
private DataInputStream input;
private DataOutputStream output;
private static final int CAPACITY = Integer.MAX_VALUE;
// For modifying the reader or writer index
private static FieldAccessor READER_INDEX;
private static FieldAccessor WRITER_INDEX;
private static final int CAPACITY = Short.MAX_VALUE;
private final DataInputStream input;
private final DataOutputStream output;
private NettyByteBufAdapter(DataInputStream input, DataOutputStream output) {
// Just pick a figure
super(CAPACITY);
this.input = input;
this.output = output;
// Prepare accessors
try {
if (READER_INDEX == null) {
@@ -73,32 +70,37 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
} catch (Exception e) {
throw new RuntimeException("Cannot initialize ByteBufAdapter.", e);
}
// "Infinite" reading/writing
if (input == null)
if (input == null) {
READER_INDEX.set(this, Integer.MAX_VALUE);
if (output == null)
}
if (output == null) {
WRITER_INDEX.set(this, Integer.MAX_VALUE);
}
}
/**
* Construct a new Minecraft packet serializer using the current byte buf adapter.
*
* @param input - the input stream.
* @return A packet serializer with a wrapped byte buf adapter.
*/
public static ByteBuf packetReader(DataInputStream input) {
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(input, null));
}
/**
* Construct a new Minecraft packet deserializer using the current byte buf adapter.
*
* @param output - the output stream.
* @return A packet serializer with a wrapped byte buf adapter.
*/
public static ByteBuf packetWriter(DataOutputStream output) {
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(null, output));
}
@Override
public int refCnt() {
return 1;
@@ -117,7 +119,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected byte _getByte(int paramInt) {
try {
return input.readByte();
return this.input.readByte();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -126,7 +128,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected short _getShort(int paramInt) {
try {
return input.readShort();
return this.input.readShort();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -135,7 +137,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected int _getUnsignedMedium(int paramInt) {
try {
return input.readUnsignedShort();
return this.input.readUnsignedShort();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -144,7 +146,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected int _getInt(int paramInt) {
try {
return input.readInt();
return this.input.readInt();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -153,7 +155,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected long _getLong(int paramInt) {
try {
return input.readLong();
return this.input.readLong();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -162,7 +164,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected void _setByte(int index, int value) {
try {
output.writeByte(value);
this.output.writeByte(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
@@ -171,7 +173,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected void _setShort(int index, int value) {
try {
output.writeShort(value);
this.output.writeShort(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
@@ -180,7 +182,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected void _setMedium(int index, int value) {
try {
output.writeShort(value);
this.output.writeShort(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
@@ -189,7 +191,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected void _setInt(int index, int value) {
try {
output.writeInt(value);
this.output.writeInt(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
@@ -198,7 +200,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected void _setLong(int index, long value) {
try {
output.writeLong(value);
this.output.writeLong(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
@@ -238,7 +240,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
try {
for (int i = 0; i < length; i++) {
dst.setByte(dstIndex + i, input.read());
dst.setByte(dstIndex + i, this.input.read());
}
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
@@ -249,7 +251,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
try {
input.read(dst, dstIndex, length);
this.input.read(dst, dstIndex, length);
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -259,7 +261,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public ByteBuf getBytes(int index, ByteBuffer dst) {
try {
dst.put(ByteStreams.toByteArray(input));
dst.put(ByteStreams.toByteArray(this.input));
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -268,14 +270,14 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public ByteBuf getBytes(int index, OutputStream dst, int length) throws IOException {
ByteStreams.copy(ByteStreams.limit(input, length), dst);
ByteStreams.copy(ByteStreams.limit(this.input, length), dst);
return this;
}
@Override
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
byte[] data = ByteStreams.toByteArray(ByteStreams.limit(input, length));
byte[] data = ByteStreams.toByteArray(ByteStreams.limit(this.input, length));
out.write(ByteBuffer.wrap(data));
return data.length;
}
@@ -284,9 +286,9 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
byte[] buffer = new byte[length];
src.getBytes(srcIndex, buffer);
try {
output.write(buffer);
this.output.write(buffer);
return this;
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
@@ -296,7 +298,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
try {
output.write(src, srcIndex, length);
this.output.write(src, srcIndex, length);
return this;
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
@@ -306,7 +308,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public ByteBuf setBytes(int index, ByteBuffer src) {
try {
WritableByteChannel channel = Channels.newChannel(output);
WritableByteChannel channel = Channels.newChannel(this.output);
channel.write(src);
return this;
@@ -318,15 +320,15 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public int setBytes(int index, InputStream in, int length) throws IOException {
InputStream limit = ByteStreams.limit(in, length);
ByteStreams.copy(limit, output);
ByteStreams.copy(limit, this.output);
return length - limit.available();
}
@Override
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(length);
WritableByteChannel channel = Channels.newChannel(output);
WritableByteChannel channel = Channels.newChannel(this.output);
int count = in.read(buffer);
channel.write(buffer);
return count;
@@ -424,7 +426,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
return 0;
}
public int setBytes(int arg0, FileChannel arg1, long arg2, int arg3) throws IOException {
public int setBytes(int arg0, FileChannel arg1, long arg2, int arg3) {
return 0;
}
@@ -1,40 +0,0 @@
package com.comphenix.protocol.injector.netty;
import java.io.DataInputStream;
import java.io.IOException;
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;
public class NettyNetworkMarker extends NetworkMarker {
public NettyNetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer) {
super(side, inputBuffer, null);
}
public NettyNetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer) {
super(side, inputBuffer, null);
}
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
// Skip the variable int containing the packet ID
getSerializer().deserializeVarInt(input);
return input;
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
// We don't have to add anything - it's already there
return buffer;
}
@Override
protected DataInputStream addHeader(DataInputStream input, PacketType type) {
// As above
return input;
}
}
@@ -1,222 +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.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.Maps;
/**
* @author dmulloy2
*/
public class NettyProtocolRegistry extends ProtocolRegistry {
public NettyProtocolRegistry() {
super();
}
@Override
protected synchronized void initialize() {
if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BEE_UPDATE)) {
initializeNew();
return;
}
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 (Object map : serverMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Object map : clientMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Object protocol : protocols) {
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;
}
@SuppressWarnings("unchecked")
private synchronized void initializeNew() {
Object[] protocols = enumProtocol.getEnumConstants();
// ID to Packet class maps
Map<Object, Map<Class<?>, Integer>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Class<?>, Integer>> clientMaps = Maps.newLinkedHashMap();
Register result = new Register();
Field mainMapField = null;
Field packetMapField = null;
// Iterate through the protocols
for (Object protocol : protocols) {
if (mainMapField == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(protocol.getClass(), true);
mainMapField = fuzzy.getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL)
.typeDerivedOf(Map.class)
.build());
mainMapField.setAccessible(true);
}
Map<Object, Object> directionMap;
try {
directionMap = (Map<Object, Object>) mainMapField.get(protocol);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to access packet map", ex);
}
for (Entry<Object, Object> entry : directionMap.entrySet()) {
Object holder = entry.getValue();
if (packetMapField == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(holder.getClass(), true);
packetMapField = fuzzy.getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL)
.typeDerivedOf(Map.class)
.build());
packetMapField.setAccessible(true);
}
Map<Class<?>, Integer> packetMap;
try {
packetMap = (Map<Class<?>, Integer>) packetMapField.get(holder);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to access packet map", ex);
}
String direction = entry.getKey().toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, packetMap);
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, packetMap);
}
}
}
// Maps we have to occasionally check have changed
// TODO: Find equivalent in Object2IntMap
/* for (Object map : serverMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Object map : clientMaps.values()) {
result.containers.add(new MapContainer(map));
} */
for (Object protocol : protocols) {
Enum<?> enumProtocol = (Enum<?>) protocol;
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
if (serverMaps.containsKey(protocol)) {
associatePackets(result, reverse(serverMaps.get(protocol)), equivalent, Sender.SERVER);
}
if (clientMaps.containsKey(protocol)) {
associatePackets(result, reverse(clientMaps.get(protocol)), equivalent, Sender.CLIENT);
}
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
/**
* Reverses a key->value map to value->key
* Non-deterministic behavior when multiple keys are mapped to the same value
*/
private <K, V> Map<V, K> reverse(Map<K, V> map) {
Map<V, K> newMap = new HashMap<>(map.size());
for (Entry<K, V> entry : map.entrySet()) {
newMap.put(entry.getValue(), entry.getKey());
}
return newMap;
}
@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());
// System.out.println(Arrays.toString(type.getClassNames()) + " -> " + 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 (Exception ex) {
ProtocolLogger.debug("Encountered an exception associating packet " + type, ex);
}
}
}
}
@@ -1,393 +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.net.SocketAddress;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.util.concurrent.EventExecutorGroup;
/**
* A pipeline proxy.
* @author Kristian
*/
public class PipelineProxy implements ChannelPipeline {
protected final ChannelPipeline pipeline;
protected final Channel channel;
public PipelineProxy(ChannelPipeline pipeline, Channel channel) {
this.pipeline = pipeline;
this.channel = channel;
}
@Override
public ChannelPipeline addAfter(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
pipeline.addAfter(arg0, arg1, arg2, arg3);
return this;
}
@Override
public ChannelPipeline addAfter(String arg0, String arg1, ChannelHandler arg2) {
pipeline.addAfter(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addBefore(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
pipeline.addBefore(arg0, arg1, arg2, arg3);
return this;
}
@Override
public ChannelPipeline addBefore(String arg0, String arg1, ChannelHandler arg2) {
pipeline.addBefore(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addFirst(ChannelHandler... arg0) {
pipeline.addFirst(arg0);
return this;
}
@Override
public ChannelPipeline addFirst(EventExecutorGroup arg0, ChannelHandler... arg1) {
pipeline.addFirst(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addFirst(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
pipeline.addFirst(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addFirst(String arg0, ChannelHandler arg1) {
pipeline.addFirst(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addLast(ChannelHandler... arg0) {
pipeline.addLast(arg0);
return this;
}
@Override
public ChannelPipeline addLast(EventExecutorGroup arg0, ChannelHandler... arg1) {
pipeline.addLast(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addLast(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
pipeline.addLast(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addLast(String arg0, ChannelHandler arg1) {
pipeline.addLast(arg0, arg1);
return this;
}
@Override
public ChannelFuture bind(SocketAddress arg0, ChannelPromise arg1) {
return pipeline.bind(arg0, arg1);
}
@Override
public ChannelFuture bind(SocketAddress arg0) {
return pipeline.bind(arg0);
}
@Override
public Channel channel() {
return channel;
}
@Override
public ChannelFuture close() {
return pipeline.close();
}
@Override
public ChannelFuture close(ChannelPromise arg0) {
return pipeline.close(arg0);
}
@Override
public ChannelFuture connect(SocketAddress arg0, ChannelPromise arg1) {
return pipeline.connect(arg0, arg1);
}
@Override
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1, ChannelPromise arg2) {
return pipeline.connect(arg0, arg1, arg2);
}
@Override
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1) {
return pipeline.connect(arg0, arg1);
}
@Override
public ChannelFuture connect(SocketAddress arg0) {
return pipeline.connect(arg0);
}
@Override
public ChannelHandlerContext context(ChannelHandler arg0) {
return pipeline.context(arg0);
}
@Override
public ChannelHandlerContext context(Class<? extends ChannelHandler> arg0) {
return pipeline.context(arg0);
}
@Override
public ChannelHandlerContext context(String arg0) {
return pipeline.context(arg0);
}
// We have to call the depreciated methods to properly implement the proxy
@Override
public ChannelFuture deregister() {
return pipeline.deregister();
}
@Override
public ChannelFuture deregister(ChannelPromise arg0) {
return pipeline.deregister(arg0);
}
@Override
public ChannelPipeline fireChannelUnregistered() {
pipeline.fireChannelUnregistered();
return this;
}
@Override
public ChannelFuture disconnect() {
return pipeline.disconnect();
}
@Override
public ChannelFuture disconnect(ChannelPromise arg0) {
return pipeline.disconnect(arg0);
}
@Override
public ChannelPipeline fireChannelActive() {
pipeline.fireChannelActive();
return this;
}
@Override
public ChannelPipeline fireChannelInactive() {
pipeline.fireChannelInactive();
return this;
}
@Override
public ChannelPipeline fireChannelRead(Object arg0) {
pipeline.fireChannelRead(arg0);
return this;
}
@Override
public ChannelPipeline fireChannelReadComplete() {
pipeline.fireChannelReadComplete();
return this;
}
@Override
public ChannelPipeline fireChannelRegistered() {
pipeline.fireChannelRegistered();
return this;
}
@Override
public ChannelPipeline fireChannelWritabilityChanged() {
pipeline.fireChannelWritabilityChanged();
return this;
}
@Override
public ChannelPipeline fireExceptionCaught(Throwable arg0) {
pipeline.fireExceptionCaught(arg0);
return this;
}
@Override
public ChannelPipeline fireUserEventTriggered(Object arg0) {
pipeline.fireUserEventTriggered(arg0);
return this;
}
@Override
public ChannelHandler first() {
return pipeline.first();
}
@Override
public ChannelHandlerContext firstContext() {
return pipeline.firstContext();
}
@Override
public ChannelPipeline flush() {
pipeline.flush();
return this;
}
@Override
public <T extends ChannelHandler> T get(Class<T> arg0) {
return pipeline.get(arg0);
}
@Override
public ChannelHandler get(String arg0) {
return pipeline.get(arg0);
}
@Override
public Iterator<Entry<String, ChannelHandler>> iterator() {
return pipeline.iterator();
}
@Override
public ChannelHandler last() {
return pipeline.last();
}
@Override
public ChannelHandlerContext lastContext() {
return pipeline.lastContext();
}
@Override
public List<String> names() {
return pipeline.names();
}
@Override
public ChannelPipeline read() {
pipeline.read();
return this;
}
@Override
public ChannelPipeline remove(ChannelHandler arg0) {
pipeline.remove(arg0);
return this;
}
@Override
public <T extends ChannelHandler> T remove(Class<T> arg0) {
return pipeline.remove(arg0);
}
@Override
public ChannelHandler remove(String arg0) {
return pipeline.remove(arg0);
}
@Override
public ChannelHandler removeFirst() {
return pipeline.removeFirst();
}
@Override
public ChannelHandler removeLast() {
return pipeline.removeLast();
}
@Override
public ChannelPipeline replace(ChannelHandler arg0, String arg1, ChannelHandler arg2) {
pipeline.replace(arg0, arg1, arg2);
return this;
}
@Override
public <T extends ChannelHandler> T replace(Class<T> arg0, String arg1, ChannelHandler arg2) {
return pipeline.replace(arg0, arg1, arg2);
}
@Override
public ChannelHandler replace(String arg0, String arg1, ChannelHandler arg2) {
return pipeline.replace(arg0, arg1, arg2);
}
@Override
public Map<String, ChannelHandler> toMap() {
return pipeline.toMap();
}
@Override
public ChannelFuture write(Object arg0, ChannelPromise arg1) {
return pipeline.write(arg0, arg1);
}
@Override
public ChannelFuture write(Object arg0) {
return pipeline.write(arg0);
}
@Override
public ChannelFuture writeAndFlush(Object arg0, ChannelPromise arg1) {
return pipeline.writeAndFlush(arg0, arg1);
}
@Override
public ChannelFuture writeAndFlush(Object arg0) {
return pipeline.writeAndFlush(arg0);
}
/* Added in Netty 4.1, seem to be unused
public ChannelFuture newFailedFuture(Throwable ex) {
return pipeline.newFailedFuture(ex);
}
public ChannelProgressivePromise newProgressivePromise() {
return pipeline.newProgressivePromise();
}
public ChannelPromise newPromise() {
return pipeline.newPromise();
}
public ChannelFuture newSucceededFuture() {
return pipeline.newSucceededFuture();
}
public ChannelPromise voidPromise() {
return pipeline.voidPromise();
}
*/
}
@@ -1,459 +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.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Set;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.injector.spigot.AbstractPacketInjector;
import com.comphenix.protocol.injector.spigot.AbstractPlayerHandler;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.Lists;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
public class ProtocolInjector implements ChannelListener {
public static final ReportType REPORT_CANNOT_INJECT_INCOMING_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
private volatile boolean injected;
private volatile boolean closed;
// The temporary player factory
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
private List<VolatileField> bootstrapFields = Lists.newArrayList();
// The channel injector factory
private InjectionFactory injectionFactory;
// List of network managers
private volatile List<Object> networkManagers;
// Different sending filters
private PacketTypeSet sendingFilters = new PacketTypeSet();
private PacketTypeSet reveivedFilters = new PacketTypeSet();
// Packets that must be executed on the main thread
private PacketTypeSet mainThreadFilters = new PacketTypeSet();
// Which packets are buffered
private PacketTypeSet bufferedPackets = new PacketTypeSet();
private ListenerInvoker invoker;
// Handle errors
private ErrorReporter reporter;
private boolean debug;
public ProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) {
this.injectionFactory = new InjectionFactory(plugin);
this.invoker = invoker;
this.reporter = reporter;
}
@Override
public boolean isDebug() {
return debug;
}
/**
* Set whether or not the debug mode is enabled.
* @param debug - TRUE if it is, FALSE otherwise.
*/
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* Inject into the spigot connection class.
*/
@SuppressWarnings("unchecked")
public synchronized void inject() {
if (injected)
throw new IllegalStateException("Cannot inject twice.");
try {
FuzzyReflection fuzzyServer = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
List<Method> serverConnectionMethods = fuzzyServer.getMethodListByParameters(MinecraftReflection.getServerConnectionClass(), new Class[] {});
// Get the server connection
Object server = fuzzyServer.getSingleton();
Object serverConnection = null;
for (Method method : serverConnectionMethods) {
try {
serverConnection = method.invoke(server);
// Continue until we get a server connection
if (serverConnection != null) {
break;
}
} catch (Exception ex) {
ProtocolLogger.debug("Encountered an exception invoking " + method, ex);
}
}
if (serverConnection == null) {
throw new ReflectiveOperationException("Failed to obtain server connection");
}
// Handle connected channels
final ChannelInboundHandler endInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(final Channel channel) throws Exception {
try {
synchronized (networkManagers) {
// For some reason it needs to be delayed on 1.12, but the delay breaks 1.11 and below
// TODO I see this more as a temporary hotfix than a permanent solution
if (MinecraftVersion.getCurrentVersion().getMinor() >= 12) {
channel.eventLoop().submit(() ->
injectionFactory.fromChannel(channel, ProtocolInjector.this, playerFactory).inject());
} else {
injectionFactory.fromChannel(channel, ProtocolInjector.this, playerFactory).inject();
}
}
} catch (Exception ex) {
reporter.reportDetailed(ProtocolInjector.this, Report.newBuilder(REPORT_CANNOT_INJECT_INCOMING_CHANNEL).messageParam(channel).error(ex));
}
}
};
// This is executed before Minecraft's channel handler
final ChannelInboundHandler beginInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
// Our only job is to add init protocol
channel.pipeline().addLast(endInitProtocol);
}
};
// Add our handler to newly created channels
final ChannelHandler connectionHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = (Channel) msg;
// Prepare to initialize ths channel
channel.pipeline().addFirst(beginInitProtocol);
ctx.fireChannelRead(msg);
}
};
FuzzyReflection fuzzy = FuzzyReflection.fromObject(serverConnection, true);
try {
Field field = fuzzy.getParameterizedField(List.class, MinecraftReflection.getNetworkManagerClass());
field.setAccessible(true);
networkManagers = (List<Object>) field.get(serverConnection);
} catch (Exception ex) {
ProtocolLogger.debug("Encountered an exception checking list fields", ex);
Method method = fuzzy.getMethodByParameters("getNetworkManagers", List.class,
new Class<?>[] { serverConnection.getClass() });
method.setAccessible(true);
networkManagers = (List<Object>) method.invoke(null, serverConnection);
}
if (networkManagers == null) {
throw new ReflectiveOperationException("Failed to obtain list of network managers");
}
// Insert ProtocolLib's connection interceptor
bootstrapFields = getBootstrapFields(serverConnection);
for (VolatileField field : bootstrapFields) {
final List<Object> list = (List<Object>) field.getValue();
// We don't have to override this list
if (list == networkManagers) {
continue;
}
// Synchronize with each list before we attempt to replace them.
field.setValue(new BootstrapList(list, connectionHandler));
}
injected = true;
} catch (Exception e) {
throw new RuntimeException("Unable to inject channel futures.", e);
}
}
@Override
public boolean hasListener(Class<?> packetClass) {
return reveivedFilters.contains(packetClass) || sendingFilters.contains(packetClass);
}
@Override
public boolean hasMainThreadListener(Class<?> packetClass) {
return mainThreadFilters.contains(packetClass);
}
@Override
public ErrorReporter getReporter() {
return reporter;
}
/**
* Inject our packet handling into a specific player.
* @param player Player to inject into
*/
public void injectPlayer(Player player) {
injectionFactory.fromPlayer(player, this).inject();
}
private List<VolatileField> getBootstrapFields(Object serverConnection) {
List<VolatileField> result = Lists.newArrayList();
// Find and (possibly) proxy every list
for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) {
VolatileField volatileField = new VolatileField(field, serverConnection, true).toSynchronized();
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) volatileField.getValue();
if (list.size() == 0 || list.get(0) instanceof ChannelFuture) {
result.add(volatileField);
}
}
return result;
}
/**
* Clean up any remaning injections.
*/
public synchronized void close() {
if (!closed) {
closed = true;
for (VolatileField field : bootstrapFields) {
Object value = field.getValue();
// Undo the processed channels, if any
if (value instanceof BootstrapList) {
((BootstrapList) value).close();
}
field.revertValue();
}
// Uninject all the players
injectionFactory.close();
}
}
@Override
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) {
Class<?> clazz = packet.getClass();
if (sendingFilters.contains(clazz) || marker != null) {
try {
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
return packetQueued(container, injector.getPlayer(), marker);
} catch (LinkageError e) {
// So far this has been seen when the jar is shared
System.err.println("[ProtocolLib] Encountered a LinkageError. Make sure you\'re not using this jar for multiple server instances!");
System.err.println("[ProtocolLib] If you\'re getting this error for other reasons, please report it!");
e.printStackTrace();
}
}
// Don't change anything
return null;
}
@Override
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) {
Class<?> clazz = packet.getClass();
if (reveivedFilters.contains(clazz) || marker != null) {
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
return packetReceived(container, injector.getPlayer(), marker);
}
// Don't change anything
return null;
}
@Override
public boolean includeBuffer(Class<?> packetClass) {
return bufferedPackets.contains(packetClass);
}
/**
* Called to inform the event listeners of a queued packet.
* @param packet - the packet that is to be sent.
* @param receiver - the receiver of this packet.
* @return The packet event that was used.
*/
private PacketEvent packetQueued(PacketContainer packet, Player receiver, NetworkMarker marker) {
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver);
invoker.invokePacketSending(event);
return event;
}
/**
* Called to inform the event listeners of a received packet.
* @param packet - the packet that has been receieved.
* @param sender - the client packet.
* @param marker - the network marker.
* @return The packet event that was used.
*/
private PacketEvent packetReceived(PacketContainer packet, Player sender, NetworkMarker marker) {
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
invoker.invokePacketRecieving(event);
return event;
}
// Server side
public PlayerInjectionHandler getPlayerInjector() {
return new AbstractPlayerHandler(sendingFilters) {
private ChannelListener listener = ProtocolInjector.this;
@Override
public int getProtocolVersion(Player player) {
return injectionFactory.fromPlayer(player, listener).getProtocolVersion();
}
@Override
public void updatePlayer(Player player) {
injectionFactory.fromPlayer(player, listener).inject();
}
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
injectionFactory.fromPlayer(player, listener).inject();
}
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
// Ignore this too
return true;
}
@Override
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
if (!type.isAsyncForced() && (options == null || !options.contains(ListenerOptions.ASYNC)))
mainThreadFilters.addType(type);
super.addPacketHandler(type, options);
}
@Override
public void removePacketHandler(PacketType type) {
mainThreadFilters.removeType(type);
super.removePacketHandler(type);
}
@Override
public boolean uninjectPlayer(Player player) {
// Just let Netty clean this up
return true;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
injectionFactory.fromPlayer(receiver, listener).sendServerPacket(packet.getHandle(), marker, filters);
}
@Override
public boolean hasMainThreadListener(PacketType type) {
return mainThreadFilters.contains(type);
}
@Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
injectionFactory.fromPlayer(player, listener).recieveClientPacket(mcPacket);
}
@Override
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
// Ignore this
return null;
}
@Override
public void handleDisconnect(Player player) {
injectionFactory.fromPlayer(player, listener).close();
}
@Override
public Channel getChannel(Player player) {
Injector injector = injectionFactory.fromPlayer(player, listener);
if (injector instanceof ChannelInjector) {
return ((ChannelInjector) injector).getChannel();
}
return null;
}
};
}
/**
* Retrieve a view of this protocol injector as a packet injector.
* @return The packet injector.
*/
// Client side
public PacketInjector getPacketInjector() {
return new AbstractPacketInjector(reveivedFilters) {
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
NetworkMarker marker = buffered != null ? new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null;
injectionFactory.fromPlayer(client, ProtocolInjector.this).saveMarker(packet.getHandle(), marker);
return packetReceived(packet, client, marker);
}
@Override
public void inputBuffersChanged(Set<PacketType> set) {
bufferedPackets = new PacketTypeSet(set);
}
};
}
}
@@ -1,125 +0,0 @@
package com.comphenix.protocol.injector.netty;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* Represents a way of accessing the new netty Protocol enum.
* @author Kristian
*/
public abstract class ProtocolRegistry {
/**
* Represents a register we are currently building.
* @author Kristian
*/
protected static class Register {
// The main lookup table
public BiMap<PacketType, Class<?>> typeToClass = HashBiMap.create();
public volatile Set<PacketType> serverPackets = Sets.newHashSet();
public volatile Set<PacketType> clientPackets = Sets.newHashSet();
public List<MapContainer> containers = Lists.newArrayList();
public Register() {
}
/**
* Determine if the current register is outdated.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isOutdated() {
for (MapContainer container : containers) {
if (container.hasChanged()) {
return true;
}
}
return false;
}
}
protected Class<?> enumProtocol;
// Current register
protected volatile Register register;
public ProtocolRegistry() {
enumProtocol = MinecraftReflection.getEnumProtocolClass();
initialize();
}
/**
* Retrieve an immutable view of the packet type lookup.
* @return The packet type lookup.
*/
public Map<PacketType, Class<?>> getPacketTypeLookup() {
return Collections.unmodifiableMap(register.typeToClass);
}
/**
* Retrieve an immutable view of the class to packet type lookup.
* @return The packet type lookup.
*/
public Map<Class<?>, PacketType> getPacketClassLookup() {
return Collections.unmodifiableMap(register.typeToClass.inverse());
}
/**
* Retrieve every known client packet, from every protocol.
* @return Every client packet.
*/
public Set<PacketType> getClientPackets() {
return Collections.unmodifiableSet(register.clientPackets);
}
/**
* Retrieve every known server packet, from every protocol.
* @return Every server packet.
*/
public Set<PacketType> getServerPackets() {
return Collections.unmodifiableSet(register.serverPackets);
}
/**
* Ensure that our local register is up-to-date with Minecraft.
* <p>
* This operation may block the calling thread.
*/
public synchronized void synchronize() {
// Check if the packet registry has changed
if (register.isOutdated()) {
initialize();
}
}
/**
* Load the packet lookup tables in each protocol.
*/
protected abstract void initialize();
protected abstract void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender);
/**
* Retrieve the number of mapping in all the maps.
* @param maps - iterable of maps.
* @return The sum of all the entries.
*/
protected final int sum(Iterable<? extends Map<Integer, Class<?>>> maps) {
int count = 0;
for (Map<Integer, Class<?>> map : maps)
count += map.size();
return count;
}
}
@@ -1,265 +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 io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.socket.SocketChannel;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
/**
* This class wraps a Netty {@link Channel} in a {@link Socket}. It overrides
* all methods in {@link Socket} to ensure that calls are not mistakingly made
* to the unsupported super socket. All operations that can be sanely applied to
* a {@link Channel} are implemented here. Those which cannot will throw an
* {@link UnsupportedOperationException}.
*/
// Thanks MD5. :)
public class SocketAdapter extends Socket {
private final SocketChannel ch;
private SocketAdapter(SocketChannel ch) {
this.ch = ch;
}
public static SocketAdapter adapt(SocketChannel ch) {
return new SocketAdapter(ch);
}
@Override
public void bind(SocketAddress bindpoint) throws IOException {
ch.bind(bindpoint).syncUninterruptibly();
}
@Override
public synchronized void close() throws IOException {
ch.close().syncUninterruptibly();
}
@Override
public void connect(SocketAddress endpoint) throws IOException {
ch.connect(endpoint).syncUninterruptibly();
}
@Override
public void connect(SocketAddress endpoint, int timeout) throws IOException {
ch.config().setConnectTimeoutMillis(timeout);
ch.connect(endpoint).syncUninterruptibly();
}
@Override
public boolean equals(Object obj) {
return obj instanceof SocketAdapter && ch.equals(((SocketAdapter) obj).ch);
}
@Override
public java.nio.channels.SocketChannel getChannel() {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public InetAddress getInetAddress() {
return ch.remoteAddress().getAddress();
}
@Override
public InputStream getInputStream() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public boolean getKeepAlive() throws SocketException {
return ch.config().getOption(ChannelOption.SO_KEEPALIVE);
}
@Override
public InetAddress getLocalAddress() {
return ch.localAddress().getAddress();
}
@Override
public int getLocalPort() {
return ch.localAddress().getPort();
}
@Override
public SocketAddress getLocalSocketAddress() {
return ch.localAddress();
}
@Override
public boolean getOOBInline() throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public int getPort() {
return ch.remoteAddress().getPort();
}
@Override
public synchronized int getReceiveBufferSize() throws SocketException {
return ch.config().getOption(ChannelOption.SO_RCVBUF);
}
@Override
public SocketAddress getRemoteSocketAddress() {
return ch.remoteAddress();
}
@Override
public boolean getReuseAddress() throws SocketException {
return ch.config().getOption(ChannelOption.SO_REUSEADDR);
}
@Override
public synchronized int getSendBufferSize() throws SocketException {
return ch.config().getOption(ChannelOption.SO_SNDBUF);
}
@Override
public int getSoLinger() throws SocketException {
return ch.config().getOption(ChannelOption.SO_LINGER);
}
@Override
public synchronized int getSoTimeout() throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public boolean getTcpNoDelay() throws SocketException {
return ch.config().getOption(ChannelOption.TCP_NODELAY);
}
@Override
public int getTrafficClass() throws SocketException {
return ch.config().getOption(ChannelOption.IP_TOS);
}
@Override
public int hashCode() {
return ch.hashCode();
}
@Override
public boolean isBound() {
return ch.localAddress() != null;
}
@Override
public boolean isClosed() {
return !ch.isOpen();
}
@Override
public boolean isConnected() {
return ch.isActive();
}
@Override
public boolean isInputShutdown() {
return ch.isInputShutdown();
}
@Override
public boolean isOutputShutdown() {
return ch.isOutputShutdown();
}
@Override
public void sendUrgentData(int data) throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setKeepAlive(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.SO_KEEPALIVE, on);
}
@Override
public void setOOBInline(boolean on) throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public synchronized void setReceiveBufferSize(int size) throws SocketException {
ch.config().setOption(ChannelOption.SO_RCVBUF, size);
}
@Override
public void setReuseAddress(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.SO_REUSEADDR, on);
}
@Override
public synchronized void setSendBufferSize(int size) throws SocketException {
ch.config().setOption(ChannelOption.SO_SNDBUF, size);
}
@Override
public void setSoLinger(boolean on, int linger) throws SocketException {
ch.config().setOption(ChannelOption.SO_LINGER, linger);
}
@Override
public synchronized void setSoTimeout(int timeout) throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setTcpNoDelay(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.TCP_NODELAY, on);
}
@Override
public void setTrafficClass(int tc) throws SocketException {
ch.config().setOption(ChannelOption.IP_TOS, tc);
}
@Override
public void shutdownInput() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void shutdownOutput() throws IOException {
ch.shutdownOutput().syncUninterruptibly();
}
@Override
public String toString() {
return ch.toString();
}
}
Loaded 100 of 333 files, more files were not shown because too many files have changed in this diff. Show more