Compare commits

...
475 Commits
Author SHA1 Message Date
Dan Mulloy 456430f055 update readme 2023-04-09 20:10:01 -05:00
Dan Mulloy 71af32aa68 fix version and deploy 2023-04-09 20:06:41 -05:00
Dan Mulloy 24204e7515 fix compiling with java 8 2023-04-09 19:34:15 -05:00
Dan Mulloy 098b3957c0 how about this? 2023-04-09 19:16:20 -05:00
Dan Mulloy 99f480765c fix some javadocs 2023-04-09 19:02:14 -05:00
Dan Mulloy 327a17ba50 fix remaining tests 2023-04-09 18:50:37 -05:00
Dan Mulloy 3991ad0e8d skip tests in codeql 2023-04-09 18:24:13 -05:00
Dan Mulloy 1ca55a3c83 grab minecraft libs from their repo 2023-04-09 18:13:50 -05:00
Dan Mulloy 76991b85c3 dependency scopes 2023-04-09 17:59:11 -05:00
Dan Mulloy b6f74a8adb no java 20 yet 2023-04-09 17:57:29 -05:00
Dan Mulloy 952aabe7b0 update github actions 2023-04-09 17:55:53 -05:00
Dan Mulloy 01d77b7d0e start migrating to gradle 2023-04-09 17:47:19 -05:00
LOOHP d83dd9ab8a Get around expensive validate calls (#2318) 2023-04-09 16:40:57 -05:00
Pasqual Koschmieder ab32f938d7 resolve packet instance construction concurrency issue (#2312) 2023-04-07 20:57:49 -05:00
Dan Mulloy c7753a9d5b Better server ping concurrency
Addresses #2289
2023-04-03 22:55:32 -04:00
libraryaddict b51812655a Correct the version from 1.19.4 to 1.19.3 (#2299) 2023-04-03 18:27:38 -05:00
libraryaddict a910edc16b Fix WrappedRegistry for versions older than 1.19.4 (#2298) 2023-04-03 11:27:12 -05:00
Dan Mulloy fb2075b774 Fix exception for null sounds
Fixes #2276
2023-03-30 17:07:34 -05:00
Dan Mulloy 4b78bf6a34 Update dependencies to support Java 20
Fixes #2274
Fixes #2270
Fixes #2251
2023-03-28 12:40:32 -05:00
libraryaddict edce5b6d0f Fix /packet arguments, fixes #1847 (#2271)
Pretty simple, it's deleting the argument twice. Which means you have to use `/packet add add <remaining>`
2023-03-27 22:03:27 -05:00
Dan Mulloy 7217b11ba7 Fix reading class aliases
Fixes #2263
2023-03-26 14:09:45 -05:00
Dan Mulloy 18c2b389a4 Improve unit tests for server ping 2023-03-26 13:58:46 -05:00
Dan Mulloy 1912a9c871 Fix setting ping player sample in 1.19.4 2023-03-26 13:31:39 -05:00
Dan Mulloy 0c6fa46871 Optimize class lookups 2023-03-25 23:16:04 -05:00
Dan Mulloy df3b68df4c Some cleanup
Fixes super critical issue #2255
2023-03-25 21:45:29 -05:00
Lukas Alt aebefded86 Packet filtering for bundled packets in 1.19.4 (#2258)
Since Minecraft 1.19.4, the protocol supports bundling consecutive packets to ensure the client processes them in one tick. However, Packet Events are not called for the individual packets in such a bundle in the current dev build of ProtocolLib. For example, no packet events are currently sent for the ENTITY_METADATA packet when an entity is first spawned as the packet is bundled with the ENTITY_SPAWN packet. However, if the entity metadata is changed later on, the event will be called.
This PR proposes to fix this by unpacking the bundled packets and invoking the packet filtering for each packet.

I also want to briefly explain how the bundling works. A bundle starts with a PACKET_DELIMITER (0x00, net.minecraft.network.protocol.BundleDelimiterPacket) packet followed by all packets that should be bundled and finished with another PACKET_DELIMITER (0x00). Within the Netty pipeline, this sequence is transformed into one synthesized packet found in net.minecraft.network.protocol.game.ClientboundBundlePacket, which is essentially just a list of packets. At the stage at which ProtocolLib injects into the clientbound netty pipeline, this packet has not been unpacked yet. Thus, we need to handle the ClientboundBundlePacket, which unfortunately is not registered in ProtocolLib. The fact that two different classes map to the same packet currently requires a dirty remapping in the packet structure modifier.
2023-03-25 21:08:31 -05:00
Dan Mulloy 64e1e7de24 Fix some issues with server ping in 1.19.4 2023-03-23 20:23:10 -05:00
Dan Mulloy 05fa147b48 WIP: 1.19.4 update 2023-03-23 13:43:22 -05:00
libraryaddict bba534d694 Fix listeners removing each other (#2236) 2023-03-16 16:52:10 -05:00
Aseeefandaseef 4f0fe72add Use ConcurrentHashMap to improve performance (#2226)
* modified hashset collections for small performance gains

* reverted load factor modification since no noticeable performance gain was seen

---------

Co-authored-by: aseef <contact@aseef.dev>
2023-03-12 13:05:19 -05:00
armagidon-exception 365bb66d2a Fixed backwards compatibility with WrappedDataValue (#2172) 2023-02-16 02:31:26 +00:00
Richy 0a41cf9353 Add a soft-cache to getFields method (#2152)
* Added a soft-cache to getFields method

* Added cache purge on cache miss
2023-02-15 21:28:02 -05:00
Lennart Lösche d0b274249c Print exception in MethodHandleHelper (#2097)
print exception caused by xyz
2023-01-14 01:00:56 +00:00
Nassim Jahnke bdbbb7cb9c Fix SynchedEntityData$DataValue mapping (#2101)
Add SynchedEntityData$DataValue mapping
2023-01-13 19:56:51 -05:00
Dan Mulloy 531f28cbaf Fix sounds in 1.19.3
Fixes #2049
2023-01-10 16:36:01 -05:00
Mathéo Cimbaro 30b69d3ecf Added PacketContainer#getUUIDLists() (#2072)
The 1.19.3 PlayerListRemove is the first one using List<UUID>

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

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

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

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

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

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

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

Fixes #373, fixes #368, fixes #360, fixes #359
2017-07-08 02:10:24 -04:00
蒋天蓓 59171c5443 Fix issue with component array converter (#372) 2017-07-07 11:42:17 -04:00
Dan Mulloy 501244cb3c Attempt to fix Jenkins build
Oddly enough it builds with no errors on my computer
2017-07-06 14:32:35 -04:00
Dan Mulloy 390c21f6d5 Add PacketEvent#isPlayerTemporary, check for player updates
This should address issues with temporary players by hopefully returning them less often
2017-07-06 13:56:30 -04:00
Dan Mulloy 20d78832b0 Update dependency versions to hopefully work with Java 9 2017-06-14 15:41:00 -04:00
Dan Mulloy adb3c5392c Release version 4.3.0 2017-06-14 14:30:46 -04:00
Dan Mulloy cd2f56d7c7 Update TinyProtocol to 1.12 2017-06-14 14:30:31 -04:00
Dan Mulloy 7c9bbc3f6c Add 1.12 protocol version 2017-06-09 15:08:04 -04:00
Dan Mulloy 52e396b25d Update to Spigot 1.12 2017-06-08 13:30:57 -04:00
Dan Mulloy aeb2cfb65e Update to 1.12-pre6
No real changes this time
2017-05-30 14:31:13 -04:00
Dan Mulloy 7f5ea6e493 Merge remote-tracking branch 'origin/master' 2017-05-29 22:14:28 -04:00
Dan Mulloy 1be94aad78 Rework packet type deprecation to actually work properly
Also fix compatibility with 1.8.0
2017-05-29 22:14:08 -04:00
dadus33 6bb169ac01 Fix 1.8 compatibility (#339)
Use proper method name ('setLenient' instead of 'setLenienent') in the deserializeLegacy method for 1.8 compatibility.
2017-05-29 12:59:01 -04:00
Dan Mulloy ea7900d64f Better ensure packet types are correct, deprecate legacy IDs 2017-05-24 17:20:27 -04:00
Dan Mulloy 123abdff30 Code cleanup, remove some ancient compatibility 2017-05-24 14:35:22 -04:00
Dan Mulloy 11247f1c01 Return an empty modifier if a class does not exist
Addresses #336
2017-05-23 18:52:28 -04:00
Dan Mulloy f093c9196d Ignore IntelliJ files 2017-05-19 11:17:31 -04:00
Dan Mulloy 81daa62071 Update to 1.12-pre5 2017-05-19 11:11:30 -04:00
Dan Mulloy f99b977888 Restore backwards compatibility, restructure poms 2017-05-18 12:02:47 -04:00
Dan Mulloy b00ae2ff15 Remove Java 8 warning
It's required, so the JVM will warn you for me
2017-05-14 21:46:18 -04:00
Dan Mulloy b4f9c501b3 1.12 hotfix: delay injection for network manager
Fixes #332, fixes #330
2017-05-14 21:37:36 -04:00
Dan Mulloy 1738c6f243 Stop testing with Java 7 2017-05-14 14:37:55 -04:00
Dan Mulloy abea7441a4 Merge branch 'master' of git+ssh://git@github.com/dmulloy2/ProtocolLib 2017-05-14 14:30:21 -04:00
Dan Mulloy 3887ba27d2 Update to the 1.12 pre-release
- Now requires Java 8
- Added new packets
- Added ChatType enum (replaces magic value)
- Missing protocol version
2017-05-14 14:28:56 -04:00
games647 76a3af84a1 Strictly match the sendPacket method
Related to #322
2017-04-27 20:49:37 -04:00
Dan Mulloy be5d42ee19 Minecraft 1.12 will use Java 8 2017-04-27 20:48:02 -04:00
Dan Mulloy 790711c16e Update version to 4.2.2-SNAPSHOT 2017-04-27 20:46:04 -04:00
Dan Mulloy 8c3127dee1 Release version 4.2.1 2017-04-09 11:56:53 -04:00
Dan Mulloy 5ec87c9d76 Fix issues with logging custom payload packets 2017-03-20 21:42:46 -04:00
Dan Mulloy 2205ce639e Merge pull request #305 from aadnk/master
Branch hiccup
2017-03-18 19:33:14 -04:00
Dan Mulloy 2844d75607 Merge pull request #127 from dmulloy2/master
Branch hiccup
2017-03-18 19:32:12 -04:00
Dan Mulloy a6810ea874 Merge branch 'master' of git+ssh://git@github.com/dmulloy2/ProtocolLib 2017-03-18 19:29:43 -04:00
Dan Mulloy aaf1af8e41 Don't worry if we can't determine the Java version 2017-03-18 19:29:16 -04:00
Dan Mulloy b8dd43aeed Merge pull request #126 from joeypoulissen/master
Added missing fallingdust particle as seen at http://wiki.vg/Protocol#Particle
2017-03-12 18:42:29 -04:00
Joey e959efc5af Added fallingdust particle as seen at http://wiki.vg/Protocol#Particle 2017-03-11 13:59:25 +01:00
Dan Mulloy 73ce01bbde Make packet logging more robust 2017-03-09 10:53:06 -05:00
Dan Mulloy b30628f6db Make hex dump more readable, match more packet names 2017-03-08 09:08:29 -05:00
Dan Mulloy 9289825d87 Add packet logging for debugging 2017-03-01 17:02:46 -05:00
Dan Mulloy 57cfbc4f81 Merge branch 'master' of git+ssh://git@github.com/dmulloy2/ProtocolLib 2017-02-04 12:12:54 -05:00
Dan Mulloy 3ab2026d62 People don't care if updater sites are down 2017-02-04 12:12:31 -05:00
Dan Mulloy e439c778a6 Use the correct JsonReader constructor in 1.8
Fixes aadnk#121
2017-01-01 16:13:47 -05:00
Dan Mulloy 98413c917e Update packet IDs 2016-12-22 12:24:18 -05:00
Dan Mulloy 540a5e53c4 Make Sound handling more robust
Fixes aadnk#119
2016-12-22 11:42:52 -05:00
Dan Mulloy 27047f83a5 Fix erroneous startup message 2016-12-22 11:33:55 -05:00
Dan Mulloy 222b535c15 Update version to 4.2.1-SNAPSHOT 2016-12-22 11:33:10 -05:00
Dan Mulloy 3f31af6b70 I'll look into signing releases later 2016-12-21 14:20:56 -05:00
Dan Mulloy b6a054b766 Release version 4.2.0 2016-12-21 14:16:57 -05:00
Dan Mulloy 3b7b692e80 Update to 1.11.2 2016-12-21 14:02:32 -05:00
Dan Mulloy c6788addeb Mark 1.11.1 as officially supported 2016-12-20 23:37:35 -05:00
Dan Mulloy 5b38bc7f38 Update to 1.11.1 2016-12-20 16:14:13 -05:00
Dan Mulloy 33bb12713d Restore ItemStack to CraftItemStack conversion 2016-12-16 12:36:35 -05:00
Dan Mulloy d175a7655e Refactor package caching, account for some edge cases 2016-11-29 19:39:11 -05:00
Dan Mulloy 4afb38aae1 Merge pull request #115 from Mast3rPlan/master
Fix performance issue
2016-11-27 12:41:02 -05:00
Emiel Tasseel 36b5670b3d Fix performance issue where classes that don't exist in the current environment endlessly get looked up over and over again 2016-11-26 12:50:27 +01:00
Dan Mulloy 7b61796506 Add mechanism to clone NonNullLists 2016-11-22 17:36:49 -05:00
Dan Mulloy 684b687e42 Rework ItemStack handling for 1.11
Note to developers: You should never supply a null ItemStack to any
method. ProtocolLib will never return a null ItemStack. Update your
plugins accordingly.
2016-11-20 16:29:23 -05:00
Dan Mulloy 7a9b9afe2e Add modifier for item lists 2016-11-19 19:18:35 -05:00
Dan Mulloy 7d382279ad Update to Minecraft 1.11 2016-11-16 21:56:46 -05:00
Dan Mulloy b4f5f18283 Strongly recommend Java 8
If you must use Java 7, set 'ignoreJava: true' in the config.
2016-10-14 19:28:17 -04:00
Dan Mulloy 17e150393a Avoid throwing errors with invalid entities 2016-10-14 19:20:47 -04:00
Dan Mulloy b5ecfb9c5f Update version to 4.2.0-SNAPSHOT 2016-10-14 19:20:16 -04:00
Dan Mulloy 31a0c9b10c Update version in plugin.yml 2016-09-18 15:27:49 -04:00
Dan Mulloy e4d95e09cf Update version to 4.1.0 for release 2016-09-18 15:27:01 -04:00
Dan Mulloy d173be0db5 Actually use UpdateSign when it exists 2016-09-18 14:42:40 -04:00
Dan Mulloy 051989a396 Merge pull request #265 from faradize/master
Added the ability of reading/writing lists of NBT classes!
2016-09-11 17:00:25 -04:00
Dan Mulloy 795c77e943 Fix minor example issue
Closes aadnk#113
2016-09-11 16:56:05 -04:00
Dan Mulloy 1de6bbc9dc Make sure we use the update sign packet when it exists
Should fix #263
2016-09-11 16:52:45 -04:00
verbuchselt 474c74b1f9 Added the ability of reading/writing lists of NBT classes!
Works perfectly with MapChunk-Packet's TileEntity-Data!
2016-09-07 11:37:30 +02:00
Dan Mulloy caaa57c915 Add basic TileEntityTest 2016-08-27 13:08:22 -04:00
Dan Mulloy 588f736348 Fix missing TileEntity write methods
Fixes #249
2016-08-27 13:07:49 -04:00
Dan Mulloy 828302150a Deal with plugins that create proxy player objects
As I was writing this I realized it sounded like Bungee support, but it
isn't
2016-08-13 13:19:13 -04:00
Dan Mulloy 144723af18 Ensure we're always using leniency with components
Fixes #252
2016-08-06 14:40:49 -04:00
Dan Mulloy 087913ab3a Standardize equals and hashCode for wrappers 2016-08-06 13:19:14 -04:00
Dan Mulloy 64942cbb5e Make much of ProtocolLib locale independent
Fixes #242
2016-07-25 14:09:23 -04:00
Dan Mulloy 97430a8e42 Add renamed OPEN_SIGN_ENTITY packet for backwards compat
Fixes #243
2016-07-25 13:47:56 -04:00
Dan Mulloy 1bdd200794 Try to fix a weird null case 2016-07-16 16:03:01 -04:00
Dan Mulloy 679035c3bf Fix an error with wire packets
Addresses #224
2016-07-16 15:46:23 -04:00
Dan Mulloy bd4352f041 Fix cloning data watchers
Addresses #224
2016-07-12 16:15:46 -04:00
Dan Mulloy 05ffeb8e7f Restore compatibility with 1.8
1.8.8 is still one of the most popular server versions. As a result,
many servers were not receiving important fixes and new APIs.

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

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

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

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

Also updated maven plugin versions
2016-02-17 21:03:26 -05:00
Dan Mulloy 8a5267ab76 Update version to 3.7-SNAPSHOT 2016-02-17 20:52:11 -05:00
Dan Mulloy a189947617 Fix issue with entity tracking on PaperSpigot 2016-02-16 15:43:23 -05:00
Dan Mulloy f01b3999ff Update version to 3.6.6-SNAPSHOT 2016-02-16 15:41:55 -05:00
495 changed files with 36131 additions and 47471 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+38
View File
@@ -0,0 +1,38 @@
Contributing to ProtocolLib
===========================
This page will detail specific things that must be done if you intend to contribute to this project.
## Updating ProtocolLib to a New Protocol Version
#### Before we get started:
1. **When do we need to update the protocol version?**
_A Minecraft protocol version is a number for each specification of the Minecraft protocol.
This means it is incremented when there is a change to the [Protocol Specification](http://wiki.vg/Protocol),
but not necessarily for each Minecraft version update. If the latest Minecraft version contains a protocol change,
that means ProtocolLib needs to be updated._
2. **When do we need to update other parts of ProtocolLib?**
_When the package version guard changes. The package version guard is the version string inserted
into the package identifier for CraftBukkit and net.minecraft.server (e.g. v1_9_R2). It is incremented
by Spigot if there is a major breaking change in the internal server code. This is to encourage plugins
that interact directly with CraftBukkit/nms code to update more specifically. However, since most of
ProtocolLib uses reflection, and the structure of packet classes is generally static, one will have
to go through the code and change imports from nms/CraftBukkit._
#### Ready? Let's get started!
1. Read the [Protocol Changes](http://wiki.vg/Protocol_History). Always make sure the list is both
complete and correct. If you're unsure, don't hesitate to ask in #mcdevs (the people who maintain wiki.vg) on [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
(or had their ID changed), make sure to update this list. If a packet was removed
in favor of usage of another packet, instead of removing it, move it to the bottom of the list
in its section, add a deprecation warning to it, and redirect it to the packet that replaced it.
4. `mvn` in the root directory to build the project.
5. If tests fail in the maven build, go through the tests to make sure you removed references to any removed packets
and changed the NMS version guards.
6. Increment the package version in `com.comphenix.protocol.utility.Constants`.
7. `com.comphenix.protocol.ProtocolLibrary` contains several constants that must be updated, including the Minecraft version
and the release date.
8. `com.comphenix.protocol.utility.MinecraftProtocolVersion` contains a map of all the protocol version integers.
If the protocol version has been incremented, add a new line to the map.
9. `mvn` in root directory again. If it builds successfully, test on the appropriate version of a Spigot server. If
the build fails, debug!
+2
View File
@@ -0,0 +1,2 @@
github: [dmulloy2]
custom: ["https://paypal.me/dmulloy2"]
+12
View File
@@ -0,0 +1,12 @@
_Follow this template except for feature requests. Use pastebin when providing /protocol dump and any relevant errors._
Make sure you've done the following:
- [ ] You're using the latest build for your server version
- [ ] This isn't 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`
Debug paste link:
Description and relevant errors:
+29
View File
@@ -0,0 +1,29 @@
---
name: API question
about: Questions related to using ProtocolLib in your plugins
title: ''
labels: ''
assignees: ''
---
**Make sure you're doing the following**
- [ ] You're using the latest build for your server version
- [ ] This isn't an issue caused by another plugin
- [ ] You've checked for duplicate issues
- [ ] You didn't use `/reload`
**Describe the question**
A clear and concise description of what your question is.
**API method(s) used**
List what API method(s) you're using
**Expected behavior**
A clear and concise description of what you expected to happen.
**Code**
If applicable, add relevant code from your project
**Additional context**
Add any other context about the problem here.
+32
View File
@@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
- [ ] This issue is not solved in a development build
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Version Info**
Provide your ProtocolLib install info with `/protocol dump` through pastebin.
**Additional context**
Add any other context about the problem here.
+22
View File
@@ -0,0 +1,22 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
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 [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+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: '19'
cache: 'gradle'
- name: Run gradle build lifecycle
run: ./gradlew build shadowJar --no-daemon
- name: Upload plugin file
uses: actions/upload-artifact@v2
with:
name: ProtocolLib
path: build/libs/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: '19'
cache: 'gradle'
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: 'java'
- name: Run gradle build lifecycle
run: ./gradlew build -x test --no-daemon
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+10 -1
View File
@@ -7,6 +7,10 @@
.classpath
.settings/
bin/
.idea/
*.iml
.ipr
.iws
*.pydevproject
.metadata
@@ -171,4 +175,9 @@ pip-log.txt
.DS_Store
# Log4J files
logs/
logs/
.gradle/
build/
logs/
.java-version
-13
View File
@@ -1,13 +0,0 @@
language: java
jdk:
- oraclejdk8
- oraclejdk7
script: mvn clean test -U
before_install: cd ProtocolLib
install: true
notifications:
email:
recipients:
- "dmulloy2@live.com"
on_success: change
on_failure: always
-167
View File
@@ -1,167 +0,0 @@
#################
## Eclipse
#################
*.pydevproject
.project
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
target/
.gitignore
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
#################
## Visual Studio
#################
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.vspscc
.builds
*.dotCover
## TODO: If you have NuGet Package Restore enabled, uncomment this
#packages/
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
# ReSharper is a .NET coding add-in
_ReSharper*
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Others
[Bb]in
[Oo]bj
sql
TestResults
*.Cache
ClientBin
stylecop.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
############
## Windows
############
# Windows image file caches
Thumbs.db
# Folder config file
Desktop.ini
#############
## Python
#############
*.py[co]
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg
# Mac crap
.DS_Store
/target
/target
-300
View File
@@ -1,300 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name>
<version>3.6.5</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>http://www.spigotmc.org/resources/protocollib.1997/</url>
<packaging>jar</packaging>
<parent>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib-Parent</artifactId>
<version>v3-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.number></project.build.number>
</properties>
<build>
<defaultGoal>clean install</defaultGoal>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.sf</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.spigotmc:spigot</exclude>
<exclude>org.spigotmc:spigot-api</exclude>
<exclude>junit:junit</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<manifestEntries>
<Main-Class>com.comphenix.protocol.Application</Main-Class>
<Implementation-Title>ProtocolLib</Implementation-Title>
<Implementation-Version>${project.version}${project.build.number}</Implementation-Version>
<Implementation-Vendor>dmulloy2</Implementation-Vendor>
</manifestEntries>
</archive>
<finalName>ProtocolLib</finalName>
</configuration>
</plugin>
<!--<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<phase>package</phase>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
<goal>javadoc</goal>
</goals>
</execution>
</executions>
</plugin>-->
</plugins>
</build>
<!--<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<threshold>High</threshold>
<effort>Default</effort>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.7</version>
<configuration>
<failOnError>false</failOnError>
<links>
<link>http://download.oracle.com/javase/6/docs/api/</link>
</links>
</configuration>
</plugin>
</plugins>
</reporting>-->
<profiles>
<profile>
<id>jenkins</id>
<activation>
<property>
<name>env.BUILD_NUMBER</name>
</property>
</activation>
<properties>
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
</properties>
</profile>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<scm>
<connection>scm:git:git://github.com/dmulloy2/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:dmulloy2/ProtocolLib.git</developerConnection>
<url>https://github.com/dmulloy2/ProtocolLib</url>
</scm>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dmulloy2</id>
<name>Dan Mulloy</name>
<url>http://shadowvolt.com/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
</roles>
</developer>
<developer>
<id>aadnk</id>
<name>Kristian S. Stangeland</name>
<email>kr_stang@hotmail.com</email>
<url>http://comphenix.net/</url>
<roles>
<role>former author</role>
</roles>
<timezone>1</timezone>
</developer>
</developers>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.8.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.comphenix.executors</groupId>
<artifactId>BukkitExecutors</artifactId>
<version>1.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -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;
import org.bukkit.Bukkit;
/**
* Ignore this class.
*
* @author Kristian
*/
public class Application {
private static Thread mainThread;
private static boolean primaryMethod = true;
public static void main(String[] args) {
// For now, though we might consider making a proper application
System.out.println("This is a Bukkit library. Place it in the plugin-folder and restart the server!");
}
/**
* Determine if we are running on the main thread.
* @return TRUE if we are, FALSE otherwise.
*/
public static boolean isPrimaryThread() {
if (primaryMethod) {
try {
return Bukkit.isPrimaryThread();
} catch (LinkageError e) {
primaryMethod = false;
}
}
// Fallback method
return Thread.currentThread().equals(mainThread);
}
/**
* Register the calling thread as the primary thread.
*/
static void registerPrimaryThread() {
mainThread = Thread.currentThread();
}
}
@@ -1,170 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import com.comphenix.protocol.async.AsyncListenerHandler;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodUtils;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.compiler.StructureCompiler;
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
/**
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
*
* @author Kristian
*/
class CleanupStaticMembers {
// Reports
public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
private ClassLoader loader;
private ErrorReporter reporter;
public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) {
this.loader = loader;
this.reporter = reporter;
}
/**
* Ensure that the previous ClassLoader is not leaking.
*/
public void resetAll() {
// This list must always be updated
@SuppressWarnings("deprecation")
Class<?>[] publicClasses = {
AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class,
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
BackgroundCompiler.class, StructureCompiler.class,
ObjectWriter.class, Packets.Server.class, Packets.Client.class,
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class,
AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class,
MinecraftReflection.class, NbtBinarySerializer.class
};
String[] internalClasses = {
"com.comphenix.protocol.events.SerializedOfflinePlayer",
"com.comphenix.protocol.injector.player.InjectedServerConnection",
"com.comphenix.protocol.injector.player.NetworkFieldInjector",
"com.comphenix.protocol.injector.player.NetworkObjectInjector",
"com.comphenix.protocol.injector.player.NetworkServerInjector",
"com.comphenix.protocol.injector.player.PlayerInjector",
"com.comphenix.protocol.injector.EntityUtilities",
"com.comphenix.protocol.injector.packet.PacketRegistry",
"com.comphenix.protocol.injector.packet.PacketInjector",
"com.comphenix.protocol.injector.packet.ReadPacketModifier",
"com.comphenix.protocol.injector.StructureCache",
"com.comphenix.protocol.reflect.compiler.BoxingHelper",
"com.comphenix.protocol.reflect.compiler.MethodDescriptor",
"com.comphenix.protocol.wrappers.nbt.WrappedElement",
};
resetClasses(publicClasses);
resetClasses(getClasses(loader, internalClasses));
}
private void resetClasses(Class<?>[] classes) {
// Reset each class one by one
for (Class<?> clazz : classes) {
resetClass(clazz);
}
}
private void resetClass(Class<?> clazz) {
for (Field field : clazz.getFields()) {
Class<?> type = field.getType();
// Only check static non-primitive fields. We also skip strings.
if (Modifier.isStatic(field.getModifiers()) &&
!type.isPrimitive() && !type.equals(String.class) &&
!type.equals(ReportType.class)) {
try {
setFinalStatic(field, null);
} catch (IllegalAccessException e) {
// Just inform the player
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
);
e.printStackTrace();
}
}
}
}
// HACK! HAACK!
private static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException {
int modifier = field.getModifiers();
boolean isFinal = Modifier.isFinal(modifier);
Field modifiersField = isFinal ? FieldUtils.getField(Field.class, "modifiers", true) : null;
// We have to remove the final field first
if (isFinal) {
FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true);
}
// Now we can safely modify the field
FieldUtils.writeStaticField(field, newValue, true);
// Revert modifier
if (isFinal) {
FieldUtils.writeField(modifiersField, field, modifier, true);
}
}
private Class<?>[] getClasses(ClassLoader loader, String[] names) {
List<Class<?>> output = new ArrayList<Class<?>>();
for (String name : names) {
try {
output.add(loader.loadClass(name));
} catch (ClassNotFoundException e) {
// Warn the user
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_UNLOAD_CLASS).error(e).messageParam(name));
}
}
return output.toArray(new Class<?>[0]);
}
}
@@ -1,114 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.netty.WirePacket;
/**
* Represents a object capable of sending or receiving packets.
*
* @author Kristian
*/
public interface PacketStream {
/**
* Send a packet to the given player.
* @param receiver - the reciever.
* @param packet - packet to send.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player receiver, PacketContainer packet)
throws InvocationTargetException;
/**
* Send a packet to the given player.
* @param receiver - the reciever.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
throws InvocationTargetException;
/**
* Send a packet to the given player.
* @param receiver - the receiver.
* @param packet - packet to send.
* @param marker - the network marker to use.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters)
throws InvocationTargetException;
/**
* Send a wire packet to the given player.
* @param receiver - the receiver.
* @param id - packet id.
* @param bytes - packet bytes.
* @throws InvocationTargetException if an error occured when sending the packet.
*/
public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException;
/**
* Send a wire packet to the given player.
* @param receiver - the receiver.
* @param packet - packet to send.
* @throws InvocationTargetException if an error occured when sending the packet.
*/
public void sendWirePacket(Player receiver, WirePacket packet) throws InvocationTargetException;
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet)
throws IllegalAccessException, InvocationTargetException;
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
throws IllegalAccessException, InvocationTargetException;
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param marker - the network marker to use.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters)
throws IllegalAccessException, InvocationTargetException;
}
File diff suppressed because it is too large. Load diff
@@ -1,346 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.util.Set;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.IntEnum;
/**
* List of known packet IDs since 1.3.2.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @author Kristian
*/
@Deprecated
public final class Packets {
/**
* The highest possible packet ID. It's unlikely that this value will ever change.
*/
public static final int MAXIMUM_PACKET_ID = 255;
/**
* The maximum number of unique packet IDs. It's unlikely this will ever change.
*/
public static final int PACKET_COUNT = 256;
/**
* List of packets sent only by the server.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @author Kristian
*/
@Deprecated
public static final class Server extends IntEnum {
/**
* The singleton instance. Can also be retrieved from the parent class.
*/
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;
}
/**
* Determine if the given packet is a valid server packet in the current version of Minecraft.
* <p>
* Use {@link PacketType#isSupported()} instead.
* @param packetID - the packet to test.
* @return TRUE if this packet is supported, FALSE otherwise.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
@Deprecated
public static boolean isSupported(int packetID) throws FieldAccessException {
return PacketFilterManager.getServerPackets().contains(packetID);
}
/**
* Retrieve every client packet the current version of Minecraft is aware of.
* @return Every supported server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
@Deprecated
public static Set<Integer> getSupported() throws FieldAccessException {
return PacketFilterManager.getServerPackets();
}
// 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;
}
/**
* Determine if the given packet is a valid client packet in the current version of Minecraft.
* @param packetID - the packet to test.
* @return TRUE if this packet is supported, FALSE otherwise.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public static boolean isSupported(int packetID) throws FieldAccessException {
return PacketFilterManager.getClientPackets().contains(packetID);
}
/**
* Retrieve every client packet the current version of Minecraft is aware of.
* @return Every supported client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public static Set<Integer> getSupported() throws FieldAccessException {
return PacketFilterManager.getClientPackets();
}
// Like above
private Client() {
super();
}
}
/**
* 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,67 +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.compat.guava;
import java.io.DataInputStream;
import java.util.Set;
import java.util.logging.Level;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.google.common.collect.Range;
/**
* @author dmulloy2
*/
public class Guava {
private static GuavaCompat compat;
static {
try {
Range.closed(1, 2);
compat = new Guava17();
} catch (Throwable ex) {
try {
Class<?> clazz = Class.forName("com.comphenix.protocol.compat.guava.Guava10");
compat = (GuavaCompat) clazz.newInstance();
} catch (Throwable ex1) {
ProtocolLibrary.log(Level.SEVERE, "Failed to create Guava 10 compat:", ex1);
}
}
}
private static GuavaCompat getCompat() {
return compat;
}
public static <C extends Comparable<C>> Range<C> closedRange(C lower, C upper) {
return getCompat().closedRange(lower, upper);
}
public static <C extends Comparable<C>> Range<C> singleton(C singleton) {
return getCompat().singletonRange(singleton);
}
public static Set<Integer> toSet(Range<Integer> range) {
return getCompat().toSet(range);
}
public static DataInputStream addHeader(DataInputStream input, PacketType type) {
return getCompat().addHeader(input, type);
}
}
@@ -1,76 +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.compat.guava;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Range;
import com.google.common.io.ByteSource;
/**
* @author dmulloy2
*/
public class Guava17 implements GuavaCompat {
@Override
public <C extends Comparable<C>> Range<C> closedRange(C lower, C upper) {
return Range.closed(lower, upper);
}
@Override
public <C extends Comparable<C>> Range<C> singletonRange(C singleton) {
return Range.singleton(singleton);
}
@Override
public Set<Integer> toSet(Range<Integer> range) {
return ContiguousSet.create(range, DiscreteDomain.integers());
}
@Override
public DataInputStream addHeader(final DataInputStream input, final PacketType type) {
ByteSource header = new ByteSource() {
@Override
public InputStream openStream() throws IOException {
byte[] data = new byte[] { (byte) type.getLegacyId() };
return new ByteArrayInputStream(data);
}
};
ByteSource data = new ByteSource() {
@Override
public InputStream openStream() throws IOException {
return input;
}
};
// Combine them into a single stream
try {
return new DataInputStream(ByteSource.concat(header, data).openStream());
} catch (IOException e) {
throw new RuntimeException("Cannot add header.", e);
}
}
}
@@ -1,28 +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.compat.netty;
import com.comphenix.protocol.injector.netty.Injector;
/**
* @author dmulloy2
*/
public interface ChannelInjector extends Injector {
WrappedChannel getChannel();
}
@@ -1,103 +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.compat.netty;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* @author dmulloy2
*/
public class LegacyProtocolRegistry extends ProtocolRegistry {
public LegacyProtocolRegistry() {
super();
}
@Override
protected void initialize() {
final Object[] protocols = enumProtocol.getEnumConstants();
List<Map<Integer, Class<?>>> serverMaps = Lists.newArrayList();
List<Map<Integer, Class<?>>> clientMaps = Lists.newArrayList();
StructureModifier<Object> modifier = null;
// Result
Register result = new Register();
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Integer, Class<?>>> maps = modifier.withTarget(protocol).withType(Map.class);
serverMaps.add(maps.read(0));
clientMaps.add(maps.read(1));
}
// Maps we have to occationally check have changed
for (Map<Integer, Class<?>> map : Iterables.concat(serverMaps, clientMaps)) {
result.containers.add(new MapContainer(map));
}
// Heuristic - there are more server packets than client packets
if (sum(clientMaps) > sum(serverMaps)) {
// Swap if this is violated
List<Map<Integer, Class<?>>> temp = serverMaps;
serverMaps = clientMaps;
clientMaps = temp;
}
for (int i = 0; i < protocols.length; i++) {
Enum<?> enumProtocol = (Enum<?>) protocols[i];
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
associatePackets(result, serverMaps.get(i), equivalent, Sender.SERVER);
associatePackets(result, clientMaps.get(i), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
@Override
protected void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
for (Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromID(protocol, sender, entry.getKey(), entry.getValue());
try {
register.typeToClass.put(type, entry.getValue());
if (sender == Sender.SERVER)
register.serverPackets.add(type);
if (sender == Sender.CLIENT)
register.clientPackets.add(type);
} catch (IllegalArgumentException ex) {
// Sometimes this happens with fake packets, just ignore it
}
}
}
}
@@ -1,99 +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.compat.netty;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.logging.Level;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.compat.netty.independent.IndependentNetty;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
/**
* @author dmulloy2
*/
public class Netty {
private static NettyCompat compat;
static {
try {
Class.forName("io.netty.buffer.ByteBuf");
compat = new IndependentNetty();
} catch (ClassNotFoundException ex) {
try {
Class<?> clazz = Class.forName("com.comphenix.protocol.compat.netty.shaded.ShadedNetty");
compat = (NettyCompat) clazz.newInstance();
} catch (Exception ex1) {
ProtocolLibrary.log(Level.SEVERE, "Failed to create legacy netty compat:", ex1);
}
}
}
private static NettyCompat getCompat() {
return compat;
}
public static boolean isIndependent() {
return compat instanceof IndependentNetty;
}
public static WrappedByteBuf createPacketBuffer() {
return getCompat().createPacketBuffer();
}
public static Class<?> getGenericFutureListenerArray() {
return getCompat().getGenericFutureListenerArray();
}
public static Class<?> getChannelHandlerContext() {
return getCompat().getChannelHandlerContext();
}
public static String toEncodedText(CompressedImage image) {
return getCompat().toEncodedText(image);
}
public static WrappedByteBuf decode(byte[] encoded) {
return getCompat().decode(encoded);
}
public static ProtocolInjector getProtocolInjector(Plugin library, PacketFilterManager packetFilterManager, ErrorReporter reporter) {
return getCompat().getProtocolInjector(library, packetFilterManager, reporter);
}
public static WrappedByteBuf packetReader(DataInputStream input) {
return getCompat().packetReader(input);
}
public static WrappedByteBuf packetWriter(DataOutputStream output) {
return getCompat().packetWriter(output);
}
public static WrappedByteBuf copiedBuffer(byte[] array) {
return getCompat().copiedBuffer(array);
}
public static WrappedByteBuf buffer() {
return getCompat().buffer();
}
}
@@ -1,55 +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.compat.netty;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
/**
* @author dmulloy2
*/
// TODO: Sort out packet readers/writers
public interface NettyCompat {
WrappedByteBuf createPacketBuffer();
WrappedByteBuf copiedBuffer(byte[] array);
WrappedByteBuf buffer();
Class<?> getGenericFutureListenerArray();
Class<?> getChannelHandlerContext();
String toEncodedText(CompressedImage image);
WrappedByteBuf decode(byte[] encoded);
ProtocolInjector getProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter);
WrappedByteBuf packetReader(DataInputStream input);
WrappedByteBuf packetWriter(DataOutputStream output);
}
@@ -1,38 +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.compat.netty;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
/**
* @author dmulloy2
*/
public interface ProtocolInjector extends ChannelListener {
PlayerInjectionHandler getPlayerInjector();
PacketInjector getPacketInjector();
void setDebug(boolean debug);
void inject();
void close();
}
@@ -1,46 +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.compat.netty;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* @author dmulloy2
*/
public interface WrappedByteBuf {
void writeBytes(ObjectInputStream input, int id) throws IOException;
Object getHandle();
int readableBytes();
void readBytes(ObjectOutputStream output, int readableBytes) throws IOException;
void readBytes(byte[] data);
void writeByte(byte b);
void writeByte(int i);
void writeBytes(byte[] bytes);
byte[] array();
}
@@ -1,26 +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.compat.netty;
/**
* @author dmulloy2
*/
public interface WrappedChannel {
void writeAndFlush(Object packet);
}
@@ -1,105 +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.compat.netty.independent;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.base64.Base64;
import io.netty.util.concurrent.GenericFutureListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.compat.netty.NettyCompat;
import com.comphenix.protocol.compat.netty.ProtocolInjector;
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
import com.google.common.base.Charsets;
/**
* @author dmulloy2
*/
public class IndependentNetty implements NettyCompat {
@Override
public WrappedByteBuf createPacketBuffer() {
ByteBuf buffer = UnpooledByteBufAllocator.DEFAULT.buffer();
Class<?> packetSerializer = MinecraftReflection.getPacketDataSerializerClass();
try {
return new NettyByteBuf((ByteBuf) packetSerializer.getConstructor(ByteBuf.class).newInstance(buffer));
} catch (Exception e) {
throw new RuntimeException("Cannot construct packet serializer.", e);
}
}
@Override
public Class<?> getGenericFutureListenerArray() {
return GenericFutureListener[].class;
}
@Override
public Class<?> getChannelHandlerContext() {
return ChannelHandlerContext.class;
}
@Override
public String toEncodedText(CompressedImage image) {
final ByteBuf buffer = Unpooled.wrappedBuffer(image.getDataCopy());
String computed = "data:" + image.getMime() + ";base64," +
Base64.encode(buffer).toString(Charsets.UTF_8);
return computed;
}
@Override
public WrappedByteBuf decode(byte[] encoded) {
return new NettyByteBuf(Base64.decode(Unpooled.wrappedBuffer(encoded)));
}
@Override
public ProtocolInjector getProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) {
return new NettyProtocolInjector(plugin, invoker, reporter);
}
@Override
public WrappedByteBuf packetReader(DataInputStream input) {
return new NettyByteBuf(NettyByteBufAdapter.packetReader(input));
}
@Override
public WrappedByteBuf packetWriter(DataOutputStream output) {
return new NettyByteBuf(NettyByteBufAdapter.packetWriter(output));
}
@Override
public WrappedByteBuf copiedBuffer(byte[] array) {
return new NettyByteBuf(Unpooled.copiedBuffer(array));
}
@Override
public WrappedByteBuf buffer() {
return new NettyByteBuf(Unpooled.buffer());
}
}
@@ -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.compat.netty.independent;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.Callable;
import com.google.common.collect.Lists;
public class NettyBootstrapList implements List<Object> {
private List<Object> delegate;
private ChannelHandler handler;
/**
* Construct a new bootstrap list.
* @param delegate - the delegate.
* @param handler - the channel handler to add.
*/
public NettyBootstrapList(List<Object> delegate, ChannelHandler handler) {
this.delegate = delegate;
this.handler = handler;
// Process all existing bootstraps
for (Object item : this) {
processElement(item);
}
}
@Override
public synchronized boolean add(Object element) {
processElement(element);
return delegate.add(element);
}
@Override
public synchronized boolean addAll(Collection<? extends Object> collection) {
List<Object> copy = Lists.newArrayList(collection);
// Process the collection before we pass it on
for (Object element : copy) {
processElement(element);
}
return delegate.addAll(copy);
}
@Override
public synchronized Object set(int index, Object element) {
Object old = delegate.set(index, element);
// Handle the old future, and the newly inserted future
if (old != element) {
unprocessElement(old);
processElement(element);
}
return old;
}
/**
* Process a single element.
* @param element - the element.
*/
protected void processElement(Object element) {
if (element instanceof ChannelFuture) {
processBootstrap((ChannelFuture) element);
}
}
/**
* Unprocess a single element.
* @param element - the element to unprocess.
*/
protected void unprocessElement(Object element) {
if (element instanceof ChannelFuture) {
unprocessBootstrap((ChannelFuture) element);
}
}
/**
* Process a single channel future.
* @param future - the future.
*/
protected void processBootstrap(ChannelFuture future) {
// Important: Must be addFirst()
future.channel().pipeline().addFirst(handler);
}
/**
* Revert any changes we made to the channel future.
* @param future - the future.
*/
protected void unprocessBootstrap(ChannelFuture future) {
final Channel channel = future.channel();
// For thread safety - see ChannelInjector.close()
channel.eventLoop().submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
channel.pipeline().remove(handler);
return null;
}
});
}
/**
* Close and revert all changes.
*/
public synchronized void close() {
for (Object element : this)
unprocessElement(element);
}
// Boiler plate
@Override
public synchronized int size() {
return delegate.size();
}
@Override
public synchronized boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean contains(Object o) {
return delegate.contains(o);
}
@Override
public synchronized Iterator<Object> iterator() {
return delegate.iterator();
}
@Override
public synchronized Object[] toArray() {
return delegate.toArray();
}
@Override
public synchronized <T> T[] toArray(T[] a) {
return delegate.toArray(a);
}
@Override
public synchronized boolean remove(Object o) {
return delegate.remove(o);
}
@Override
public synchronized boolean containsAll(Collection<?> c) {
return delegate.containsAll(c);
}
@Override
public synchronized boolean addAll(int index, Collection<? extends Object> c) {
return delegate.addAll(index, c);
}
@Override
public synchronized boolean removeAll(Collection<?> c) {
return delegate.removeAll(c);
}
@Override
public synchronized boolean retainAll(Collection<?> c) {
return delegate.retainAll(c);
}
@Override
public synchronized void clear() {
delegate.clear();
}
@Override
public synchronized Object get(int index) {
return delegate.get(index);
}
@Override
public synchronized void add(int index, Object element) {
delegate.add(index, element);
}
@Override
public synchronized Object remove(int index) {
return delegate.remove(index);
}
@Override
public synchronized int indexOf(Object o) {
return delegate.indexOf(o);
}
@Override
public synchronized int lastIndexOf(Object o) {
return delegate.lastIndexOf(o);
}
@Override
public synchronized ListIterator<Object> listIterator() {
return delegate.listIterator();
}
@Override
public synchronized ListIterator<Object> listIterator(int index) {
return delegate.listIterator(index);
}
@Override
public synchronized List<Object> subList(int fromIndex, int toIndex) {
return delegate.subList(fromIndex, toIndex);
}
// End boiler plate
}
@@ -1,83 +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.compat.netty.independent;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
/**
* @author dmulloy2
*/
public class NettyByteBuf implements WrappedByteBuf {
private WeakReference<ByteBuf> handle;
public NettyByteBuf(ByteBuf handle) {
this.handle = new WeakReference<ByteBuf>(handle);
}
@Override
public void writeBytes(ObjectInputStream input, int id) throws IOException {
handle.get().writeBytes(input, id);
}
@Override
public Object getHandle() {
return handle.get();
}
@Override
public int readableBytes() {
return handle.get().readableBytes();
}
@Override
public void readBytes(ObjectOutputStream output, int readableBytes) throws IOException {
handle.get().readBytes(output, readableBytes);
}
@Override
public void readBytes(byte[] data) {
handle.get().readBytes(data);
}
@Override
public void writeByte(byte b) {
handle.get().writeByte(b);
}
@Override
public void writeByte(int i) {
handle.get().writeByte(i);
}
@Override
public void writeBytes(byte[] bytes) {
handle.get().writeBytes(bytes);
}
@Override
public byte[] array() {
return handle.get().array();
}
}
@@ -1,38 +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.compat.netty.independent;
import io.netty.channel.Channel;
import com.comphenix.protocol.compat.netty.WrappedChannel;
/**
* @author dmulloy2
*/
public class NettyChannel implements WrappedChannel {
private final Channel channel;
public NettyChannel(Channel channel) {
this.channel = channel;
}
@Override
public void writeAndFlush(Object packet) {
channel.writeAndFlush(packet);
}
}
@@ -1,932 +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.compat.netty.independent;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.internal.TypeParameterMatcher;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.ListIterator;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.compat.netty.ChannelInjector;
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
import com.comphenix.protocol.compat.netty.WrappedChannel;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.NetworkProcessor;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.NettyNetworkMarker;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.google.common.base.Preconditions;
import com.google.common.collect.MapMaker;
/**
* Represents a channel injector.
* @author Kristian
*/
public class NettyChannelInjector extends ByteToMessageDecoder implements ChannelInjector {
public static final ReportType REPORT_CANNOT_INTERCEPT_SERVER_PACKET = new ReportType("Unable to intercept a written server packet.");
public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet.");
public static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread.");
public static final ReportType REPORT_CANNOT_FIND_GET_VERSION = new ReportType("Cannot find getVersion() in NetworkMananger");
public static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s");
/**
* Indicates that a packet has bypassed packet listeners.
*/
private static final PacketEvent BYPASSED_PACKET = new PacketEvent(NettyChannelInjector.class);
// The login packet
private static Class<?> PACKET_LOGIN_CLIENT = null;
private static FieldAccessor LOGIN_GAME_PROFILE = null;
// Saved accessors
private static MethodAccessor DECODE_BUFFER;
private static MethodAccessor ENCODE_BUFFER;
private static FieldAccessor ENCODER_TYPE_MATCHER;
// For retrieving the protocol
private static FieldAccessor PROTOCOL_ACCESSOR;
// For retrieving the protocol version
private static MethodAccessor PROTOCOL_VERSION;
// The factory that created this injector
private NettyInjectionFactory factory;
// The player, or temporary player
private Player player;
private Player updated;
private String playerName;
// The player connection
private Object playerConnection;
// The current network manager and channel
private final Object networkManager;
private final Channel originalChannel;
private VolatileField channelField;
// Known network markers
private ConcurrentMap<Object, NetworkMarker> packetMarker = new MapMaker().weakKeys().makeMap();
/**
* Indicate that this packet has been processed by event listeners.
* <p>
* This must never be set outside the channel pipeline's thread.
*/
private PacketEvent currentEvent;
/**
* A packet event that should be processed by the write method.
*/
private PacketEvent finalEvent;
/**
* A flag set by the main thread to indiciate that a packet should not be processed.
*/
private final ThreadLocal<Boolean> scheduleProcessPackets = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return true;
};
};
// Other handlers
private ByteToMessageDecoder vanillaDecoder;
private MessageToByteEncoder<Object> vanillaEncoder;
private Deque<PacketEvent> finishQueue = new ArrayDeque<PacketEvent>();
// The channel listener
private ChannelListener channelListener;
// Processing network markers
private NetworkProcessor processor;
// Closed
private boolean injected;
private boolean closed;
/**
* Construct a new channel injector.
* @param player - the current player, or temporary player.
* @param networkManager - its network manager.
* @param channel - its channel.
* @param channelListener - a listener.
* @param factory - the factory that created this injector
*/
public NettyChannelInjector(Player player, Object networkManager, Channel channel, ChannelListener channelListener, NettyInjectionFactory factory) {
this.player = Preconditions.checkNotNull(player, "player cannot be NULL");
this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL");
this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL");
this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL");
this.factory = Preconditions.checkNotNull(factory, "factory cannot be NULL");
this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter());
// Get the channel field
this.channelField = new VolatileField(FuzzyReflection.fromObject(networkManager, true).getFieldByType("channel", Channel.class),
networkManager, true);
}
/**
* Get the version of the current protocol.
* @return The version.
*/
@Override
public int getProtocolVersion() {
MethodAccessor accessor = PROTOCOL_VERSION;
if (accessor == null) {
try {
accessor = Accessors.getMethodAccessor(networkManager.getClass(), "getVersion");
} catch (Throwable ex) {
}
}
if (accessor != null) {
return (Integer) accessor.invoke(networkManager);
} else {
return MinecraftProtocolVersion.getCurrentVersion();
}
}
@Override
@SuppressWarnings("unchecked")
public boolean inject() {
synchronized (networkManager) {
if (closed)
return false;
if (originalChannel instanceof Factory)
return false;
if (!originalChannel.isActive())
return false;
// Main thread? We should synchronize with the channel thread, otherwise we might see a
// pipeline with only some of the handlers removed
if (Bukkit.isPrimaryThread()) {
// Just like in the close() method, we'll avoid blocking the main thread
executeInChannelThread(new Runnable() {
@Override
public void run() {
inject();
}
});
return false; // We don't know
}
// Don't inject the same channel twice
if (findChannelHandler(originalChannel, NettyChannelInjector.class) != null) {
return false;
}
// Get the vanilla decoder, so we don't have to replicate the work
vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder");
vanillaEncoder = (MessageToByteEncoder<Object>) originalChannel.pipeline().get("encoder");
if (vanillaDecoder == null)
throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline() );
if (vanillaEncoder == null)
throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline() );
patchEncoder(vanillaEncoder);
if (DECODE_BUFFER == null)
DECODE_BUFFER = Accessors.getMethodAccessor(vanillaDecoder.getClass(),
"decode", ChannelHandlerContext.class, ByteBuf.class, List.class);
if (ENCODE_BUFFER == null)
ENCODE_BUFFER = Accessors.getMethodAccessor(vanillaEncoder.getClass(),
"encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
// Intercept sent packets
MessageToByteEncoder<Object> protocolEncoder = new MessageToByteEncoder<Object>() {
@Override
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
if (packet instanceof WirePacket) {
// Special case for wire format
NettyChannelInjector.this.encodeWirePacket((WirePacket) packet, new NettyByteBuf(output));
} else {
NettyChannelInjector.this.encode(ctx, packet, output);
}
}
@Override
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
super.write(ctx, packet, promise);
NettyChannelInjector.this.finalWrite(ctx, packet, promise);
}
};
// Intercept recieved packets
ChannelInboundHandlerAdapter finishHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// Execute context first
ctx.fireChannelRead(msg);
NettyChannelInjector.this.finishRead(ctx, msg);
}
};
// Insert our handlers - note that we effectively replace the vanilla encoder/decoder
originalChannel.pipeline().addBefore("decoder", "protocol_lib_decoder", this);
originalChannel.pipeline().addBefore("protocol_lib_decoder", "protocol_lib_finish", finishHandler);
originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder);
// Intercept all write methods
channelField.setValue(new NettyChannelProxy(originalChannel, MinecraftReflection.getPacketClass()) {
// Compatibility with Spigot 1.8
private final NettyPipelineProxy pipelineProxy = new NettyPipelineProxy(originalChannel.pipeline(), this) {
@Override
public ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler) {
// Correct the position of the decoder
if ("decoder".equals(baseName)) {
if (super.get("protocol_lib_decoder") != null && guessCompression(handler)) {
super.addBefore("protocol_lib_decoder", name, handler);
return this;
}
}
return super.addBefore(baseName, name, handler);
}
};
@Override
public ChannelPipeline pipeline() {
return pipelineProxy;
}
@Override
protected <T> Callable<T> onMessageScheduled(final Callable<T> callable, FieldAccessor packetAccessor) {
final PacketEvent event = handleScheduled(callable, packetAccessor);
// Handle cancelled events
if (event != null && event.isCancelled())
return null;
return new Callable<T>() {
@Override
public T call() throws Exception {
T result = null;
// This field must only be updated in the pipeline thread
currentEvent = event;
result = callable.call();
currentEvent = null;
return result;
}
};
}
@Override
protected Runnable onMessageScheduled(final Runnable runnable, FieldAccessor packetAccessor) {
final PacketEvent event = handleScheduled(runnable, packetAccessor);
// Handle cancelled events
if (event != null && event.isCancelled())
return null;
return new Runnable() {
@Override
public void run() {
currentEvent = event;
runnable.run();
currentEvent = null;
}
};
}
protected PacketEvent handleScheduled(Object instance, FieldAccessor accessor) {
// Let the filters handle this packet
Object original = accessor.get(instance);
// See if we've been instructed not to process packets
if (!scheduleProcessPackets.get()) {
NetworkMarker marker = getMarker(original);
if (marker != null) {
PacketEvent result = new PacketEvent(NettyChannelInjector.class);
result.setNetworkMarker(marker);
return result;
} else {
return BYPASSED_PACKET;
}
}
PacketEvent event = processSending(original);
if (event != null && !event.isCancelled()) {
Object changed = event.getPacket().getHandle();
// Change packet to be scheduled
if (original != changed)
accessor.set(instance, changed);
};
return event != null ? event : BYPASSED_PACKET;
}
});
injected = true;
return true;
}
}
/**
* Determine if the given object is a compressor or decompressor.
* @param handler - object to test.
* @return TRUE if it is, FALSE if not or unknown.
*/
private boolean guessCompression(ChannelHandler handler) {
String className = handler != null ? handler.getClass().getCanonicalName() : "";
return className.contains("Compressor") || className.contains("Decompressor");
}
/**
* Process a given message on the packet listeners.
* @param message - the message/packet.
* @return The resulting message/packet.
*/
private PacketEvent processSending(Object message) {
return channelListener.onPacketSending(NettyChannelInjector.this, message, getMarker(message));
}
/**
* This method patches the encoder so that it skips already created packets.
* @param encoder - the encoder to patch.
*/
private void patchEncoder(MessageToByteEncoder<Object> encoder) {
if (ENCODER_TYPE_MATCHER == null) {
ENCODER_TYPE_MATCHER = Accessors.getFieldAccessor(encoder.getClass(), "matcher", true);
}
ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass()));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (channelListener.isDebug())
cause.printStackTrace();
super.exceptionCaught(ctx, cause);
}
protected void encodeWirePacket(WirePacket packet, WrappedByteBuf output) throws Exception {
packet.writeId(output);
packet.writeBytes(output);
}
/**
* Encode a packet to a byte buffer, taking over for the standard Minecraft encoder.
* @param ctx - the current context.
* @param packet - the packet to encode to a byte array.
* @param output - the output byte array.
* @throws Exception If anything went wrong.
*/
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
NetworkMarker marker = null;
PacketEvent event = currentEvent;
try {
// Skip every kind of non-filtered packet
if (!scheduleProcessPackets.get()) {
return;
}
// This packet has not been seen by the main thread
if (event == null) {
Class<?> clazz = packet.getClass();
// Schedule the transmission on the main thread instead
if (channelListener.hasMainThreadListener(clazz)) {
// Delay the packet
scheduleMainThread(packet);
packet = null;
} else {
event = processSending(packet);
// Handle the output
if (event != null) {
packet = !event.isCancelled() ? event.getPacket().getHandle() : null;
}
}
}
if (event != null) {
// Retrieve marker without accidentally constructing it
marker = NetworkMarker.getNetworkMarker(event);
}
// Process output handler
if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) {
ByteBuf packetBuffer = ctx.alloc().buffer();
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, packetBuffer);
// Let each handler prepare the actual output
byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer));
// Write the result
output.writeBytes(data);
packet = null;
// Sent listeners?
finalEvent = event;
return;
}
} catch (Exception e) {
channelListener.getReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_INTERCEPT_SERVER_PACKET).callerParam(packet).error(e).build());
} finally {
// Attempt to handle the packet nevertheless
if (packet != null) {
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, output);
finalEvent = event;
}
}
}
/**
* Invoked when a packet has been written to the channel.
* @param ctx - current context.
* @param packet - the packet that has been written.
* @param promise - a promise.
*/
protected void finalWrite(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) {
PacketEvent event = finalEvent;
if (event != null) {
// Necessary to prevent infinite loops
finalEvent = null;
currentEvent = null;
processor.invokePostEvent(event, NetworkMarker.getNetworkMarker(event));
}
}
private void scheduleMainThread(final Object packetCopy) {
// Don't use BukkitExecutors for this - it has a bit of overhead
Bukkit.getScheduler().scheduleSyncDelayedTask(factory.getPlugin(), new Runnable() {
@Override
public void run() {
invokeSendPacket(packetCopy);
}
});
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception {
byteBuffer.markReaderIndex();
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
try {
// Reset queue
finishQueue.clear();
for (ListIterator<Object> it = packets.listIterator(); it.hasNext(); ) {
Object input = it.next();
Class<?> packetClass = input.getClass();
NetworkMarker marker = null;
// Special case!
handleLogin(packetClass, input);
if (channelListener.includeBuffer(packetClass)) {
byteBuffer.resetReaderIndex();
marker = new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer));
}
PacketEvent output = channelListener.onPacketReceiving(this, input, marker);
// Handle packet changes
if (output != null) {
if (output.isCancelled()) {
it.remove();
continue;
} else if (output.getPacket().getHandle() != input) {
it.set(output.getPacket().getHandle());
}
finishQueue.addLast(output);
}
}
} catch (Exception e) {
channelListener.getReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build());
}
}
/**
* Invoked after our decoder.
* @param ctx - current context.
* @param msg - the current packet.
*/
protected void finishRead(ChannelHandlerContext ctx, Object msg) {
// Assume same order
PacketEvent event = finishQueue.pollFirst();
if (event != null) {
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
if (marker != null) {
processor.invokePostEvent(event, marker);
}
}
}
/**
* Invoked when we may need to handle the login packet.
* @param packetClass - the packet class.
* @param packet - the packet.
*/
protected void handleLogin(Class<?> packetClass, Object packet) {
Class<?> loginClass = PACKET_LOGIN_CLIENT;
FieldAccessor loginClient = LOGIN_GAME_PROFILE;
// Initialize packet class and login
if (loginClass == null) {
loginClass = PacketType.Login.Client.START.getPacketClass();
PACKET_LOGIN_CLIENT = loginClass;
}
if (loginClient == null) {
loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, MinecraftReflection.getGameProfileClass(), true);
LOGIN_GAME_PROFILE = loginClient;
}
// See if we are dealing with the login packet
if (loginClass.equals(packetClass)) {
// GameProfile profile = (GameProfile) loginClient.get(packet);
WrappedGameProfile profile = WrappedGameProfile.fromHandle(loginClient.get(packet));
// Save the channel injector
factory.cacheInjector(profile.getName(), this);
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
// See NetworkManager.channelActive(ChannelHandlerContext) for why
if (channelField != null) {
channelField.refreshValue();
}
}
/**
* Retrieve every byte in the given byte buffer.
* @param buffer - the buffer.
* @return The bytes.
*/
private byte[] getBytes(ByteBuf buffer) {
byte[] data = new byte[buffer.readableBytes()];
buffer.readBytes(data);
return data;
}
/**
* Disconnect the current player.
* @param message - the disconnect message, if possible.
*/
private void disconnect(String message) {
// If we're logging in, we can only close the channel
if (playerConnection == null || player instanceof Factory) {
originalChannel.disconnect();
} else {
// Call the disconnect method
try {
MinecraftMethods.getDisconnectMethod(playerConnection.getClass()).
invoke(playerConnection, message);
} catch (Exception e) {
throw new IllegalArgumentException("Unable to invoke disconnect method.", e);
}
}
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
saveMarker(packet, marker);
try {
scheduleProcessPackets.set(filtered);
invokeSendPacket(packet);
} finally {
scheduleProcessPackets.set(true);
}
}
/**
* Invoke the sendPacket method in Minecraft.
* @param packet - the packet to send.
*/
private void invokeSendPacket(Object packet) {
// Attempt to send the packet with NetworkMarker.handle(), or the PlayerConnection if its active
try {
if (player instanceof Factory) {
MinecraftMethods.getNetworkManagerHandleMethod().invoke(networkManager, packet, new GenericFutureListener[0]);
} else {
MinecraftMethods.getSendPacketMethod().invoke(getPlayerConnection(), packet);
}
} catch (Throwable ex) {
ProtocolLibrary.getErrorReporter().reportWarning(factory.getPlugin(),
Report.newBuilder(REPORT_CANNOT_SEND_PACKET).messageParam(packet, playerName).error(ex).build());
}
}
@Override
public void recieveClientPacket(final Object packet) {
// TODO: Ensure the packet listeners are executed in the channel thread.
// Execute this in the channel thread
Runnable action = new Runnable() {
@Override
public void run() {
try {
MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(networkManager, null, packet);
} catch (Exception e) {
// Inform the user
ProtocolLibrary.getErrorReporter().reportMinimal(factory.getPlugin(), "recieveClientPacket", e);
}
}
};
// Execute in the worker thread
if (originalChannel.eventLoop().inEventLoop()) {
action.run();
} else {
originalChannel.eventLoop().execute(action);
}
}
@Override
public Protocol getCurrentProtocol() {
if (PROTOCOL_ACCESSOR == null) {
PROTOCOL_ACCESSOR = Accessors.getFieldAccessor(
networkManager.getClass(), MinecraftReflection.getEnumProtocolClass(), true);
}
return Protocol.fromVanilla((Enum<?>) PROTOCOL_ACCESSOR.get(networkManager));
}
/**
* Retrieve the player connection of the current player.
* @return The player connection.
*/
private Object getPlayerConnection() {
if (playerConnection == null) {
playerConnection = MinecraftFields.getPlayerConnection(player);
}
return playerConnection;
}
@Override
public NetworkMarker getMarker(Object packet) {
return packetMarker.get(packet);
}
@Override
public void saveMarker(Object packet, NetworkMarker marker) {
if (marker != null) {
packetMarker.put(packet, marker);
}
}
@Override
public Player getPlayer() {
if (player == null && playerName != null) {
return Bukkit.getPlayer(playerName);
}
return player;
}
/**
* Set the player instance.
* @param player - current instance.
*/
@Override
public void setPlayer(Player player) {
this.player = player;
this.playerName = player.getName();
}
/**
* Set the updated player instance.
* @param updated - updated instance.
*/
@Override
public void setUpdatedPlayer(Player updated) {
this.updated = updated;
this.playerName = updated.getName();
}
@Override
public boolean isInjected() {
return injected;
}
/**
* Determine if this channel has been closed and cleaned up.
* @return TRUE if it has, FALSE otherwise.
*/
@Override
public boolean isClosed() {
return closed;
}
@Override
public void close() {
if (!closed) {
closed = true;
if (injected) {
channelField.revertValue();
// Calling remove() in the main thread will block the main thread, which may lead
// to a deadlock:
// http://pastebin.com/L3SBVKzp
//
// ProtocolLib executes this close() method through a PlayerQuitEvent in the main thread,
// which has implicitly aquired a lock on SimplePluginManager (see SimplePluginManager.callEvent(Event)).
// Unfortunately, the remove() method will schedule the removal on one of the Netty worker threads if
// it's called from a different thread, blocking until the removal has been confirmed.
//
// This is bad enough (Rule #1: Don't block the main thread), but the real trouble starts if the same
// worker thread happens to be handling a server ping connection when this removal task is scheduled.
// In that case, it may attempt to invoke an asynchronous ServerPingEvent (see PacketStatusListener)
// using SimplePluginManager.callEvent(). But, since this has already been locked by the main thread,
// we end up with a deadlock. The main thread is waiting for the worker thread to process the task, and
// the worker thread is waiting for the main thread to finish executing PlayerQuitEvent.
//
// TL;DR: Concurrency is hard.
executeInChannelThread(new Runnable() {
@Override
public void run() {
String[] handlers = new String[] {
"protocol_lib_decoder", "protocol_lib_finish", "protocol_lib_encoder"
};
for (String handler : handlers) {
try {
originalChannel.pipeline().remove(handler);
} catch (NoSuchElementException e) {
// Ignore
}
}
}
});
// Clear cache
factory.invalidate(player);
// Clear player instances
// Should help fix memory leaks
this.player = null;
this.updated = null;
}
}
}
/**
* Execute a specific command in the channel thread.
* <p>
* Exceptions are printed through the standard error reporter mechanism.
* @param command - the command to execute.
*/
private void executeInChannelThread(final Runnable command) {
originalChannel.eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
command.run();
} catch (Exception e) {
ProtocolLibrary.getErrorReporter().reportDetailed(NettyChannelInjector.this,
Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build());
}
}
});
}
/**
* Find the first channel handler that is assignable to a given type.
* @param channel - the channel.
* @param clazz - the type.
* @return The first handler, or NULL.
*/
public static ChannelHandler findChannelHandler(Channel channel, Class<?> clazz) {
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
if (clazz.isAssignableFrom(entry.getValue().getClass())) {
return entry.getValue();
}
}
return null;
}
/**
* Represents a socket injector that foreards to the current channel injector.
* @author Kristian
*/
public static class ChannelSocketInjector implements SocketInjector {
private final NettyChannelInjector injector;
public ChannelSocketInjector(NettyChannelInjector injector) {
this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL");
}
@Override
public Socket getSocket() throws IllegalAccessException {
return NettySocketAdapter.adapt((SocketChannel) injector.originalChannel);
}
@Override
public SocketAddress getAddress() throws IllegalAccessException {
return injector.originalChannel.remoteAddress();
}
@Override
public void disconnect(String message) throws InvocationTargetException {
injector.disconnect(message);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
injector.sendServerPacket(packet, marker, filtered);
}
@Override
public Player getPlayer() {
return injector.getPlayer();
}
@Override
public Player getUpdatedPlayer() {
return injector.updated;
}
@Override
public void transferState(SocketInjector delegate) {
// Do nothing
}
@Override
public void setUpdatedPlayer(Player updatedPlayer) {
injector.setPlayer(updatedPlayer);
}
public NettyChannelInjector getChannelInjector() {
return injector;
}
}
@Override
public WrappedChannel getChannel() {
return new NettyChannel(originalChannel);
}
}
@@ -1,334 +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.compat.netty.independent;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelProgressivePromise;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import java.lang.reflect.Field;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.Callable;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.google.common.collect.Maps;
public abstract class NettyChannelProxy implements Channel {
// Mark that a certain object does not contain a message field
private static final FieldAccessor MARK_NO_MESSAGE = new FieldAccessor() {
@Override
public void set(Object instance, Object value) { }
@Override
public Object get(Object instance) { return null; }
@Override
public Field getField() { return null; };
};
// Looking up packets in inner classes
private static Map<Class<?>, FieldAccessor> MESSAGE_LOOKUP = Maps.newConcurrentMap();
// The underlying channel
protected Channel delegate;
protected Class<?> messageClass;
// Event loop proxy
private transient NettyEventLoopProxy loopProxy;
public NettyChannelProxy(Channel delegate, Class<?> messageClass) {
this.delegate = delegate;
this.messageClass = messageClass;
}
/**
* Invoked when a packet is scheduled for transmission in the event loop.
* @param <T> Type
* @param callable - callable to schedule for execution.
* @param packetAccessor - accessor for modifying the packet in the callable.
* @return The callable that will be scheduled, or NULL to cancel.
*/
protected abstract <T> Callable<T> onMessageScheduled(Callable<T> callable, FieldAccessor packetAccessor);
/**
* Invoked when a packet is scheduled for transmission in the event loop.
* @param runnable - the runnable that contains a packet to be scheduled.
* @param packetAccessor - accessor for modifying the packet in the runnable.
* @return The runnable that will be scheduled, or NULL to cancel.
*/
protected abstract Runnable onMessageScheduled(Runnable runnable, FieldAccessor packetAccessor);
@Override
public <T> Attribute<T> attr(AttributeKey<T> paramAttributeKey) {
return delegate.attr(paramAttributeKey);
}
@Override
public ChannelFuture bind(SocketAddress paramSocketAddress) {
return delegate.bind(paramSocketAddress);
}
@Override
public ChannelPipeline pipeline() {
return delegate.pipeline();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress) {
return delegate.connect(paramSocketAddress);
}
@Override
public ByteBufAllocator alloc() {
return delegate.alloc();
}
@Override
public ChannelPromise newPromise() {
return delegate.newPromise();
}
@Override
public EventLoop eventLoop() {
if (loopProxy == null) {
loopProxy = new NettyEventLoopProxy() {
@Override
protected EventLoop getDelegate() {
return delegate.eventLoop();
}
@Override
protected Runnable schedulingRunnable(final Runnable runnable) {
final FieldAccessor accessor = getMessageAccessor(runnable);
if (accessor != null) {
Runnable result = onMessageScheduled(runnable, accessor);;
return result != null ? result : getEmptyRunnable();
}
return runnable;
}
@Override
protected <T> Callable<T> schedulingCallable(Callable<T> callable) {
FieldAccessor accessor = getMessageAccessor(callable);
if (accessor != null) {
Callable<T> result = onMessageScheduled(callable, accessor);;
return result != null ? result : NettyEventLoopProxy.<T>getEmptyCallable();
}
return callable;
}
};
}
return loopProxy;
}
/**
* Retrieve a way to access the packet field of an object.
* @param value - the object.
* @return The packet field accessor, or NULL if not found.
*/
private FieldAccessor getMessageAccessor(Object value) {
Class<?> clazz = value.getClass();
FieldAccessor accessor = MESSAGE_LOOKUP.get(clazz);
if (accessor == null) {
try {
accessor = Accessors.getFieldAccessor(clazz, messageClass, true);
} catch (IllegalArgumentException e) {
accessor = MARK_NO_MESSAGE;
}
// Save the result
MESSAGE_LOOKUP.put(clazz, accessor);
}
return accessor != MARK_NO_MESSAGE ? accessor : null;
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress1,
SocketAddress paramSocketAddress2) {
return delegate.connect(paramSocketAddress1, paramSocketAddress2);
}
@Override
public ChannelProgressivePromise newProgressivePromise() {
return delegate.newProgressivePromise();
}
@Override
public Channel parent() {
return delegate.parent();
}
@Override
public ChannelConfig config() {
return delegate.config();
}
@Override
public ChannelFuture newSucceededFuture() {
return delegate.newSucceededFuture();
}
@Override
public boolean isOpen() {
return delegate.isOpen();
}
@Override
public ChannelFuture disconnect() {
return delegate.disconnect();
}
@Override
public boolean isRegistered() {
return delegate.isRegistered();
}
@Override
public ChannelFuture newFailedFuture(Throwable paramThrowable) {
return delegate.newFailedFuture(paramThrowable);
}
@Override
public ChannelFuture close() {
return delegate.close();
}
@Override
public boolean isActive() {
return delegate.isActive();
}
@Override
@Deprecated
public ChannelFuture deregister() {
return delegate.deregister();
}
@Override
public ChannelPromise voidPromise() {
return delegate.voidPromise();
}
@Override
public ChannelMetadata metadata() {
return delegate.metadata();
}
@Override
public ChannelFuture bind(SocketAddress paramSocketAddress,
ChannelPromise paramChannelPromise) {
return delegate.bind(paramSocketAddress, paramChannelPromise);
}
@Override
public SocketAddress localAddress() {
return delegate.localAddress();
}
@Override
public SocketAddress remoteAddress() {
return delegate.remoteAddress();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress,
ChannelPromise paramChannelPromise) {
return delegate.connect(paramSocketAddress, paramChannelPromise);
}
@Override
public ChannelFuture closeFuture() {
return delegate.closeFuture();
}
@Override
public boolean isWritable() {
return delegate.isWritable();
}
@Override
public Channel flush() {
return delegate.flush();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress1,
SocketAddress paramSocketAddress2, ChannelPromise paramChannelPromise) {
return delegate.connect(paramSocketAddress1, paramSocketAddress2, paramChannelPromise);
}
@Override
public Channel read() {
return delegate.read();
}
@Override
public Unsafe unsafe() {
return delegate.unsafe();
}
@Override
public ChannelFuture disconnect(ChannelPromise paramChannelPromise) {
return delegate.disconnect(paramChannelPromise);
}
@Override
public ChannelFuture close(ChannelPromise paramChannelPromise) {
return delegate.close(paramChannelPromise);
}
@Override
@Deprecated
public ChannelFuture deregister(ChannelPromise paramChannelPromise) {
return delegate.deregister(paramChannelPromise);
}
@Override
public ChannelFuture write(Object paramObject) {
return delegate.write(paramObject);
}
@Override
public ChannelFuture write(Object paramObject, ChannelPromise paramChannelPromise) {
return delegate.write(paramObject, paramChannelPromise);
}
@Override
public ChannelFuture writeAndFlush(Object paramObject, ChannelPromise paramChannelPromise) {
return delegate.writeAndFlush(paramObject, paramChannelPromise);
}
@Override
public ChannelFuture writeAndFlush(Object paramObject) {
return delegate.writeAndFlush(paramObject);
}
@Override
public int compareTo(Channel o) {
return delegate.compareTo(o);
}
}
@@ -1,259 +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.compat.netty.independent;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ProgressivePromise;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* An event loop proxy.
* @author Kristian.
*/
abstract class NettyEventLoopProxy implements EventLoop {
private static final Runnable EMPTY_RUNNABLE = new Runnable() {
@Override
public void run() {
// Do nothing
}
};
private static final Callable<?> EMPTY_CALLABLE = new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
};
};
/**
* Retrieve the underlying event loop.
* @return The event loop.
*/
protected abstract EventLoop getDelegate();
/**
* Retrieve a callable that does nothing but return NULL.
* @return The empty callable.
*/
@SuppressWarnings("unchecked")
public static <T> Callable<T> getEmptyCallable() {
return (Callable<T>) EMPTY_CALLABLE;
}
/**
* Retrieve a runnable that does nothing.
* @return A NO-OP runnable.
*/
public static Runnable getEmptyRunnable() {
return EMPTY_RUNNABLE;
}
/**
* Invoked when a runnable is being scheduled.
* @param runnable - the runnable that is scheduling.
* @return The runnable to schedule instead. Cannot be NULL.
*/
protected abstract Runnable schedulingRunnable(Runnable runnable);
/**
* Invoked when a callable is being scheduled.
* @param runnable - the callable that is scheduling.
* @return The callable to schedule instead. Cannot be NULL.
*/
protected abstract <T> Callable<T> schedulingCallable(Callable<T> callable);
@Override
public void execute(Runnable command) {
getDelegate().execute(schedulingRunnable(command));
}
@Override
public <T> Future<T> submit(Callable<T> action) {
return getDelegate().submit(schedulingCallable(action));
}
@Override
public <T> Future<T> submit(Runnable action, T arg1) {
return getDelegate().submit(schedulingRunnable(action), arg1);
}
@Override
public Future<?> submit(Runnable action) {
return getDelegate().submit(schedulingRunnable(action));
}
@Override
public <V> ScheduledFuture<V> schedule(Callable<V> action, long arg1, TimeUnit arg2) {
return getDelegate().schedule(schedulingCallable(action), arg1, arg2);
}
@Override
public ScheduledFuture<?> schedule(Runnable action, long arg1, TimeUnit arg2) {
return getDelegate().schedule(schedulingRunnable(action), arg1, arg2);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable action, long arg1, long arg2, TimeUnit arg3) {
return getDelegate().scheduleAtFixedRate(schedulingRunnable(action), arg1, arg2, arg3);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable action, long arg1, long arg2, TimeUnit arg3) {
return getDelegate().scheduleWithFixedDelay(schedulingRunnable(action), arg1, arg2, arg3);
}
// Boiler plate:
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return getDelegate().awaitTermination(timeout, unit);
}
@Override
public boolean inEventLoop() {
return getDelegate().inEventLoop();
}
@Override
public boolean inEventLoop(Thread arg0) {
return getDelegate().inEventLoop(arg0);
}
@Override
public boolean isShutdown() {
return getDelegate().isShutdown();
}
@Override
public boolean isTerminated() {
return getDelegate().isTerminated();
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
return getDelegate().invokeAll(tasks);
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
TimeUnit unit) throws InterruptedException {
return getDelegate().invokeAll(tasks, timeout, unit);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException,
ExecutionException {
return getDelegate().invokeAny(tasks);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return getDelegate().invokeAny(tasks, timeout, unit);
}
@Override
public boolean isShuttingDown() {
return getDelegate().isShuttingDown();
}
@Override
public Iterator<EventExecutor> iterator() {
return getDelegate().iterator();
}
@Override
public <V> Future<V> newFailedFuture(Throwable arg0) {
return getDelegate().newFailedFuture(arg0);
}
@Override
public EventLoop next() {
return ((EventLoopGroup) getDelegate()).next();
}
@Override
public <V> ProgressivePromise<V> newProgressivePromise() {
return getDelegate().newProgressivePromise();
}
@Override
public <V> Promise<V> newPromise() {
return getDelegate().newPromise();
}
@Override
public <V> Future<V> newSucceededFuture(V arg0) {
return getDelegate().newSucceededFuture(arg0);
}
@Override
public EventLoopGroup parent() {
return getDelegate().parent();
}
@Override
public ChannelFuture register(Channel arg0, ChannelPromise arg1) {
return getDelegate().register(arg0, arg1);
}
@Override
public ChannelFuture register(Channel arg0) {
return getDelegate().register(arg0);
}
@Override
public Future<?> shutdownGracefully() {
return getDelegate().shutdownGracefully();
}
@Override
public Future<?> shutdownGracefully(long arg0, long arg1, TimeUnit arg2) {
return getDelegate().shutdownGracefully(arg0, arg1, arg2);
}
@Override
public Future<?> terminationFuture() {
return getDelegate().terminationFuture();
}
@Override
@Deprecated
public void shutdown() {
getDelegate().shutdown();
}
@Override
@Deprecated
public List<Runnable> shutdownNow() {
return getDelegate().shutdownNow();
}
}
@@ -1,239 +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.compat.netty.independent;
import io.netty.channel.Channel;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.compat.netty.independent.NettyChannelInjector.ChannelSocketInjector;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.ClosedInjector;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
/**
* Represents an injector factory.
* <p>
* Note that the factory will return {@link ClosedInjector} when the factory is closed.
* @author Kristian
*/
public class NettyInjectionFactory {
// This should work as long as the injectors are, uh, injected
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
// Whether or not the factory is closed
private volatile boolean closed;
// The current plugin
private final Plugin plugin;
public NettyInjectionFactory(Plugin plugin) {
this.plugin = plugin;
}
/**
* Retrieve the main plugin associated with this injection factory.
* @return The main plugin.
*/
public Plugin getPlugin() {
return plugin;
}
/**
* Construct or retrieve a channel injector from an existing Bukkit player.
* @param player - the existing Bukkit player.
* @param listener - the listener.
* @return A new injector, an existing injector associated with this player, or a closed injector.
*/
@Nonnull
public Injector fromPlayer(Player player, ChannelListener listener) {
if (closed)
return new ClosedInjector(player);
Injector injector = playerLookup.get(player);
// Find a temporary injector as well
if (injector == null)
injector = getTemporaryInjector(player);
if (injector != null && !injector.isClosed())
return injector;
Object networkManager = MinecraftFields.getNetworkManager(player);
// Must be a temporary Bukkit player
if (networkManager == null) {
return fromName(player.getName(), player);
}
Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true);
// See if a channel has already been created
injector = (NettyChannelInjector) NettyChannelInjector.findChannelHandler(channel, NettyChannelInjector.class);
if (injector != null) {
// Update the player instance
playerLookup.remove(injector.getPlayer());
injector.setPlayer(player);
} else {
injector = new NettyChannelInjector(player, networkManager, channel, listener, this);
}
// Cache injector and return
cacheInjector(player, injector);
return injector;
}
/**
* Retrieve a cached injector from a name.
* <p>
* The injector may be NULL if the plugin has been reloaded during a player login.
* @param name - the name.
* @param player - the player.
* @return The cached injector, or a closed injector if it could not be found.
*/
public Injector fromName(String name, Player player) {
if (!closed) {
Injector injector = nameLookup.get(name);
// We can only retrieve cached injectors
if (injector != null) {
// Update instance
injector.setUpdatedPlayer(player);
return injector;
}
}
return new ClosedInjector(player);
}
/**
* Construct a new channel injector for the given channel.
* @param channel - the channel.
* @param listener - the listener.
* @param playerFactory - a temporary player creator.
* @return The channel injector, or a closed injector.
*/
@Nonnull
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
if (closed)
return new ClosedInjector(null);
Object networkManager = findNetworkManager(channel);
Player temporaryPlayer = playerFactory.createTemporaryPlayer(Bukkit.getServer());
NettyChannelInjector injector = new NettyChannelInjector(temporaryPlayer, networkManager, channel, listener, this);
// Initialize temporary player
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, new ChannelSocketInjector(injector));
return injector;
}
/**
* Invalidate a cached injector.
* @param player - the associated player.
* @return The cached injector, or NULL if nothing was cached.
*/
public Injector invalidate(Player player) {
Injector injector = playerLookup.remove(player);
nameLookup.remove(player.getName());
return injector;
}
/**
* Cache an injector by player.
* @param player - the player.
* @param injector - the injector to cache.
* @return The previously cached injector.
*/
public Injector cacheInjector(Player player, Injector injector) {
nameLookup.put(player.getName(), injector);
return playerLookup.put(player, injector);
}
/**
* Cache an injector by name alone.
* @param name - the name to lookup.
* @param injector - the injector.
* @return The cached injector.
*/
public Injector cacheInjector(String name, Injector injector) {
return nameLookup.put(name, injector);
}
/**
* Retrieve the associated channel injector.
* @param player - the temporary player, or normal Bukkit player.
* @return The associated injector, or NULL if this is a Bukkit player.
*/
private NettyChannelInjector getTemporaryInjector(Player player) {
SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector != null) {
return ((ChannelSocketInjector) injector).getChannelInjector();
}
return null;
}
/**
* Find the network manager in a channel's pipeline.
* @param channel - the channel.
* @return The network manager.
*/
private Object findNetworkManager(Channel channel) {
// Find the network manager
Object networkManager = NettyChannelInjector.findChannelHandler(channel, MinecraftReflection.getNetworkManagerClass());
if (networkManager != null)
return networkManager;
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
}
/**
* Determine if the factory is closed.
* <p>
* If it is, all new injectors will be closed by default.
* @return TRUE if it is closed, FALSE otherwise.
*/
public boolean isClosed() {
return closed;
}
/**
* Close all injectors created by this factory, and cease the creation of new injections.
*/
public synchronized void close() {
if (!closed) {
closed = true;
// Close everything
for (Injector injector : playerLookup.values())
injector.close();
for (Injector injector : nameLookup.values())
injector.close();
}
}
}
@@ -1,371 +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.compat.netty.independent;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.util.concurrent.EventExecutorGroup;
import java.net.SocketAddress;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* A pipeline proxy.
* @author Kristian
*/
public class NettyPipelineProxy implements ChannelPipeline {
protected final ChannelPipeline pipeline;
protected final Channel channel;
public NettyPipelineProxy(ChannelPipeline pipeline, Channel channel) {
this.pipeline = pipeline;
this.channel = channel;
}
@Override
public ChannelPipeline addAfter(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
pipeline.addAfter(arg0, arg1, arg2, arg3);
return this;
}
@Override
public ChannelPipeline addAfter(String arg0, String arg1, ChannelHandler arg2) {
pipeline.addAfter(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addBefore(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
pipeline.addBefore(arg0, arg1, arg2, arg3);
return this;
}
@Override
public ChannelPipeline addBefore(String arg0, String arg1, ChannelHandler arg2) {
pipeline.addBefore(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addFirst(ChannelHandler... arg0) {
pipeline.addFirst(arg0);
return this;
}
@Override
public ChannelPipeline addFirst(EventExecutorGroup arg0, ChannelHandler... arg1) {
pipeline.addFirst(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addFirst(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
pipeline.addFirst(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addFirst(String arg0, ChannelHandler arg1) {
pipeline.addFirst(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addLast(ChannelHandler... arg0) {
pipeline.addLast(arg0);
return this;
}
@Override
public ChannelPipeline addLast(EventExecutorGroup arg0, ChannelHandler... arg1) {
pipeline.addLast(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addLast(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
pipeline.addLast(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addLast(String arg0, ChannelHandler arg1) {
pipeline.addLast(arg0, arg1);
return this;
}
@Override
public ChannelFuture bind(SocketAddress arg0, ChannelPromise arg1) {
return pipeline.bind(arg0, arg1);
}
@Override
public ChannelFuture bind(SocketAddress arg0) {
return pipeline.bind(arg0);
}
@Override
public Channel channel() {
return channel;
}
@Override
public ChannelFuture close() {
return pipeline.close();
}
@Override
public ChannelFuture close(ChannelPromise arg0) {
return pipeline.close(arg0);
}
@Override
public ChannelFuture connect(SocketAddress arg0, ChannelPromise arg1) {
return pipeline.connect(arg0, arg1);
}
@Override
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1, ChannelPromise arg2) {
return pipeline.connect(arg0, arg1, arg2);
}
@Override
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1) {
return pipeline.connect(arg0, arg1);
}
@Override
public ChannelFuture connect(SocketAddress arg0) {
return pipeline.connect(arg0);
}
@Override
public ChannelHandlerContext context(ChannelHandler arg0) {
return pipeline.context(arg0);
}
@Override
public ChannelHandlerContext context(Class<? extends ChannelHandler> arg0) {
return pipeline.context(arg0);
}
@Override
public ChannelHandlerContext context(String arg0) {
return pipeline.context(arg0);
}
// We have to call the depreciated methods to properly implement the proxy
@Override
public ChannelFuture deregister() {
return pipeline.deregister();
}
@Override
public ChannelFuture deregister(ChannelPromise arg0) {
return pipeline.deregister(arg0);
}
@Override
public ChannelPipeline fireChannelUnregistered() {
pipeline.fireChannelUnregistered();
return this;
}
@Override
public ChannelFuture disconnect() {
return pipeline.disconnect();
}
@Override
public ChannelFuture disconnect(ChannelPromise arg0) {
return pipeline.disconnect(arg0);
}
@Override
public ChannelPipeline fireChannelActive() {
pipeline.fireChannelActive();
return this;
}
@Override
public ChannelPipeline fireChannelInactive() {
pipeline.fireChannelInactive();
return this;
}
@Override
public ChannelPipeline fireChannelRead(Object arg0) {
pipeline.fireChannelRead(arg0);
return this;
}
@Override
public ChannelPipeline fireChannelReadComplete() {
pipeline.fireChannelReadComplete();
return this;
}
@Override
public ChannelPipeline fireChannelRegistered() {
pipeline.fireChannelRegistered();
return this;
}
@Override
public ChannelPipeline fireChannelWritabilityChanged() {
pipeline.fireChannelWritabilityChanged();
return this;
}
@Override
public ChannelPipeline fireExceptionCaught(Throwable arg0) {
pipeline.fireExceptionCaught(arg0);
return this;
}
@Override
public ChannelPipeline fireUserEventTriggered(Object arg0) {
pipeline.fireUserEventTriggered(arg0);
return this;
}
@Override
public ChannelHandler first() {
return pipeline.first();
}
@Override
public ChannelHandlerContext firstContext() {
return pipeline.firstContext();
}
@Override
public ChannelPipeline flush() {
pipeline.flush();
return this;
}
@Override
public <T extends ChannelHandler> T get(Class<T> arg0) {
return pipeline.get(arg0);
}
@Override
public ChannelHandler get(String arg0) {
return pipeline.get(arg0);
}
@Override
public Iterator<Entry<String, ChannelHandler>> iterator() {
return pipeline.iterator();
}
@Override
public ChannelHandler last() {
return pipeline.last();
}
@Override
public ChannelHandlerContext lastContext() {
return pipeline.lastContext();
}
@Override
public List<String> names() {
return pipeline.names();
}
@Override
public ChannelPipeline read() {
pipeline.read();
return this;
}
@Override
public ChannelPipeline remove(ChannelHandler arg0) {
pipeline.remove(arg0);
return this;
}
@Override
public <T extends ChannelHandler> T remove(Class<T> arg0) {
return pipeline.remove(arg0);
}
@Override
public ChannelHandler remove(String arg0) {
return pipeline.remove(arg0);
}
@Override
public ChannelHandler removeFirst() {
return pipeline.removeFirst();
}
@Override
public ChannelHandler removeLast() {
return pipeline.removeLast();
}
@Override
public ChannelPipeline replace(ChannelHandler arg0, String arg1, ChannelHandler arg2) {
pipeline.replace(arg0, arg1, arg2);
return this;
}
@Override
public <T extends ChannelHandler> T replace(Class<T> arg0, String arg1, ChannelHandler arg2) {
return pipeline.replace(arg0, arg1, arg2);
}
@Override
public ChannelHandler replace(String arg0, String arg1, ChannelHandler arg2) {
return pipeline.replace(arg0, arg1, arg2);
}
@Override
public Map<String, ChannelHandler> toMap() {
return pipeline.toMap();
}
@Override
public ChannelFuture write(Object arg0, ChannelPromise arg1) {
return pipeline.write(arg0, arg1);
}
@Override
public ChannelFuture write(Object arg0) {
return pipeline.write(arg0);
}
@Override
public ChannelFuture writeAndFlush(Object arg0, ChannelPromise arg1) {
return pipeline.writeAndFlush(arg0, arg1);
}
@Override
public ChannelFuture writeAndFlush(Object arg0) {
return pipeline.writeAndFlush(arg0);
}
}
@@ -1,441 +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.compat.netty.independent;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Set;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.compat.netty.ChannelInjector;
import com.comphenix.protocol.compat.netty.ProtocolInjector;
import com.comphenix.protocol.compat.netty.WrappedChannel;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.NettyNetworkMarker;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.injector.spigot.AbstractPacketInjector;
import com.comphenix.protocol.injector.spigot.AbstractPlayerHandler;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Lists;
public class NettyProtocolInjector implements ProtocolInjector {
public static final ReportType REPORT_CANNOT_INJECT_INCOMING_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
private volatile boolean injected;
private volatile boolean closed;
// The temporary player factory
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
private List<VolatileField> bootstrapFields = Lists.newArrayList();
// The channel injector factory
private NettyInjectionFactory injectionFactory;
// List of network managers
private volatile List<Object> networkManagers;
// Different sending filters
private PacketTypeSet sendingFilters = new PacketTypeSet();
private PacketTypeSet reveivedFilters = new PacketTypeSet();
// Packets that must be executed on the main thread
private PacketTypeSet mainThreadFilters = new PacketTypeSet();
// Which packets are buffered
private PacketTypeSet bufferedPackets = new PacketTypeSet();
private ListenerInvoker invoker;
// Handle errors
private ErrorReporter reporter;
private boolean debug;
public NettyProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) {
this.injectionFactory = new NettyInjectionFactory(plugin);
this.invoker = invoker;
this.reporter = reporter;
}
@Override
public boolean isDebug() {
return debug;
}
/**
* Set whether or not the debug mode is enabled.
* @param debug - TRUE if it is, FALSE otherwise.
*/
@Override
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* Inject into the spigot connection class.
*/
@Override
@SuppressWarnings("unchecked")
public synchronized void inject() {
if (injected)
throw new IllegalStateException("Cannot inject twice.");
try {
FuzzyReflection fuzzyServer = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
List<Method> serverConnectionMethods = fuzzyServer.getMethodListByParameters(MinecraftReflection.getServerConnectionClass(), new Class[] {});
// Get the server connection
Object server = fuzzyServer.getSingleton();
Object serverConnection = null;
for (Method method : serverConnectionMethods) {
try {
serverConnection = method.invoke(server);
// Continue until we get a server connection
if (serverConnection != null) {
break;
}
} catch (Exception e) {
// Try the next though
e.printStackTrace();
}
}
// Handle connected channels
final ChannelInboundHandler endInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
try {
// This can take a while, so we need to stop the main thread from interfering
synchronized (networkManagers) {
injectionFactory.fromChannel(channel, NettyProtocolInjector.this, playerFactory).inject();
}
} catch (Exception e) {
reporter.reportDetailed(NettyProtocolInjector.this, Report.newBuilder(REPORT_CANNOT_INJECT_INCOMING_CHANNEL).messageParam(channel).error(e));
}
}
};
// This is executed before Minecraft's channel handler
final ChannelInboundHandler beginInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
// Our only job is to add init protocol
channel.pipeline().addLast(endInitProtocol);
}
};
// Add our handler to newly created channels
final ChannelHandler connectionHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = (Channel) msg;
// Prepare to initialize ths channel
channel.pipeline().addFirst(beginInitProtocol);
ctx.fireChannelRead(msg);
}
};
// Get the current NetworkMananger list
networkManagers = (List<Object>) FuzzyReflection.fromObject(serverConnection, true).invokeMethod(null, "getNetworkManagers", List.class, serverConnection);
// Insert ProtocolLib's connection interceptor
bootstrapFields = getBootstrapFields(serverConnection);
for (VolatileField field : bootstrapFields) {
final List<Object> list = (List<Object>) field.getValue();
// We don't have to override this list
if (list == networkManagers) {
continue;
}
// Synchronize with each list before we attempt to replace them.
field.setValue(new NettyBootstrapList(list, connectionHandler));
}
injected = true;
} catch (Exception e) {
throw new RuntimeException("Unable to inject channel futures.", e);
}
}
@Override
public boolean hasListener(Class<?> packetClass) {
return reveivedFilters.contains(packetClass) || sendingFilters.contains(packetClass);
}
@Override
public boolean hasMainThreadListener(Class<?> packetClass) {
return mainThreadFilters.contains(packetClass);
}
@Override
public ErrorReporter getReporter() {
return reporter;
}
/**
* Inject our packet handling into a specific player.
* @param player Player to inject into
*/
public void injectPlayer(Player player) {
injectionFactory.fromPlayer(player, this).inject();
}
private List<VolatileField> getBootstrapFields(Object serverConnection) {
List<VolatileField> result = Lists.newArrayList();
// Find and (possibly) proxy every list
for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) {
VolatileField volatileField = new VolatileField(field, serverConnection, true).toSynchronized();
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) volatileField.getValue();
if (list.size() == 0 || list.get(0) instanceof ChannelFuture) {
result.add(volatileField);
}
}
return result;
}
/**
* Clean up any remaning injections.
*/
@Override
public synchronized void close() {
if (!closed) {
closed = true;
for (VolatileField field : bootstrapFields) {
Object value = field.getValue();
// Undo the processed channels, if any
if (value instanceof NettyBootstrapList) {
((NettyBootstrapList) value).close();
}
field.revertValue();
}
// Uninject all the players
injectionFactory.close();
}
}
@Override
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) {
Class<?> clazz = packet.getClass();
if (sendingFilters.contains(clazz) || marker != null) {
try {
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
return packetQueued(container, injector.getPlayer(), marker);
} catch (LinkageError e) {
// So far this has been seen when the jar is shared
System.err.println("[ProtocolLib] Encountered a LinkageError. Make sure you\'re not using this jar for multiple server instances!");
System.err.println("[ProtocolLib] If you\'re getting this error for other reasons, please report it!");
e.printStackTrace();
}
}
// Don't change anything
return null;
}
@Override
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) {
Class<?> clazz = packet.getClass();
if (reveivedFilters.contains(clazz) || marker != null) {
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
return packetReceived(container, injector.getPlayer(), marker);
}
// Don't change anything
return null;
}
@Override
public boolean includeBuffer(Class<?> packetClass) {
return bufferedPackets.contains(packetClass);
}
/**
* Called to inform the event listeners of a queued packet.
* @param packet - the packet that is to be sent.
* @param receiver - the receiver of this packet.
* @return The packet event that was used.
*/
private PacketEvent packetQueued(PacketContainer packet, Player receiver, NetworkMarker marker) {
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver);
invoker.invokePacketSending(event);
return event;
}
/**
* Called to inform the event listeners of a received packet.
* @param packet - the packet that has been receieved.
* @param sender - the client packet.
* @param marker - the network marker.
* @return The packet event that was used.
*/
private PacketEvent packetReceived(PacketContainer packet, Player sender, NetworkMarker marker) {
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
invoker.invokePacketRecieving(event);
return event;
}
// Server side
@Override
public PlayerInjectionHandler getPlayerInjector() {
return new AbstractPlayerHandler(sendingFilters) {
private ChannelListener listener = NettyProtocolInjector.this;
@Override
public int getProtocolVersion(Player player) {
return injectionFactory.fromPlayer(player, listener).getProtocolVersion();
}
@Override
public void updatePlayer(Player player) {
injectionFactory.fromPlayer(player, listener).inject();
}
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
injectionFactory.fromPlayer(player, listener).inject();
}
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
// Ignore this too
return true;
}
@Override
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
if (options != null && !type.forceAsync() && !options.contains(ListenerOptions.ASYNC))
mainThreadFilters.addType(type);
super.addPacketHandler(type, options);
}
@Override
public void removePacketHandler(PacketType type) {
mainThreadFilters.removeType(type);
super.removePacketHandler(type);
}
@Override
public boolean uninjectPlayer(Player player) {
// Just let Netty clean this up
return true;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
injectionFactory.fromPlayer(receiver, listener).sendServerPacket(packet.getHandle(), marker, filters);
}
@Override
public boolean hasMainThreadListener(PacketType type) {
return mainThreadFilters.contains(type);
}
@Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
injectionFactory.fromPlayer(player, listener).recieveClientPacket(mcPacket);
}
@Override
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
// Ignore this
return null;
}
@Override
public void handleDisconnect(Player player) {
injectionFactory.fromPlayer(player, listener).close();
}
@Override
public WrappedChannel getChannel(Player player) {
Injector injector = injectionFactory.fromPlayer(player, listener);
if (injector instanceof ChannelInjector) {
return ((ChannelInjector) injector).getChannel();
}
return null;
}
};
}
/**
* Retrieve a view of this protocol injector as a packet injector.
* @return The packet injector.
*/
// Client side
@Override
public PacketInjector getPacketInjector() {
return new AbstractPacketInjector(reveivedFilters) {
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
NetworkMarker marker = buffered != null ? new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null;
injectionFactory.fromPlayer(client, NettyProtocolInjector.this).saveMarker(packet.getHandle(), marker);
return packetReceived(packet, client, marker);
}
@Override
public void inputBuffersChanged(Set<PacketType> set) {
bufferedPackets = new PacketTypeSet(set);
}
};
}
}
@@ -1,104 +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.compat.netty.independent;
import java.util.Map;
import java.util.Map.Entry;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.collect.Maps;
/**
* @author dmulloy2
*/
public class NettyProtocolRegistry extends ProtocolRegistry {
@Override
protected synchronized void initialize() {
Object[] protocols = enumProtocol.getEnumConstants();
// ID to Packet class maps
Map<Object, Map<Integer, Class<?>>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Integer, Class<?>>> clientMaps = Maps.newLinkedHashMap();
Register result = new Register();
StructureModifier<Object> modifier = null;
// Iterate through the protocols
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Object, Map<Integer, Class<?>>>> maps = modifier.withTarget(protocol).withType(Map.class);
for (Entry<Object, Map<Integer, Class<?>>> entry : maps.read(0).entrySet()) {
String direction = entry.getKey().toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, entry.getValue());
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, entry.getValue());
}
}
}
// Maps we have to occationally check have changed
for (Map<Integer, Class<?>> map : serverMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Map<Integer, Class<?>> map : clientMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (int i = 0; i < protocols.length; i++) {
Object protocol = protocols[i];
Enum<?> enumProtocol = (Enum<?>) protocol;
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
if (serverMaps.containsKey(protocol))
associatePackets(result, serverMaps.get(protocol), equivalent, Sender.SERVER);
if (clientMaps.containsKey(protocol))
associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
@Override
protected void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
for (Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromCurrent(protocol, sender, entry.getKey(), entry.getValue());
try {
register.typeToClass.put(type, entry.getValue());
if (sender == Sender.SERVER)
register.serverPackets.add(type);
if (sender == Sender.CLIENT)
register.clientPackets.add(type);
} catch (IllegalArgumentException ex) {
// Sometimes this happens with fake packets, just ignore it
}
}
}
}
@@ -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.compat.netty.independent;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.socket.SocketChannel;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
/**
* This class wraps a Netty {@link Channel} in a {@link Socket}. It overrides
* all methods in {@link Socket} to ensure that calls are not mistakingly made
* to the unsupported super socket. All operations that can be sanely applied to
* a {@link Channel} are implemented here. Those which cannot will throw an
* {@link UnsupportedOperationException}.
*/
// Thanks MD5. :)
public class NettySocketAdapter extends Socket {
private final SocketChannel ch;
private NettySocketAdapter(SocketChannel ch) {
this.ch = ch;
}
public static NettySocketAdapter adapt(SocketChannel ch) {
return new NettySocketAdapter(ch);
}
@Override
public void bind(SocketAddress bindpoint) throws IOException {
ch.bind(bindpoint).syncUninterruptibly();
}
@Override
public synchronized void close() throws IOException {
ch.close().syncUninterruptibly();
}
@Override
public void connect(SocketAddress endpoint) throws IOException {
ch.connect(endpoint).syncUninterruptibly();
}
@Override
public void connect(SocketAddress endpoint, int timeout) throws IOException {
ch.config().setConnectTimeoutMillis(timeout);
ch.connect(endpoint).syncUninterruptibly();
}
@Override
public boolean equals(Object obj) {
return obj instanceof NettySocketAdapter && ch.equals(((NettySocketAdapter) obj).ch);
}
@Override
public java.nio.channels.SocketChannel getChannel() {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public InetAddress getInetAddress() {
return ch.remoteAddress().getAddress();
}
@Override
public InputStream getInputStream() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public boolean getKeepAlive() throws SocketException {
return ch.config().getOption(ChannelOption.SO_KEEPALIVE);
}
@Override
public InetAddress getLocalAddress() {
return ch.localAddress().getAddress();
}
@Override
public int getLocalPort() {
return ch.localAddress().getPort();
}
@Override
public SocketAddress getLocalSocketAddress() {
return ch.localAddress();
}
@Override
public boolean getOOBInline() throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public int getPort() {
return ch.remoteAddress().getPort();
}
@Override
public synchronized int getReceiveBufferSize() throws SocketException {
return ch.config().getOption(ChannelOption.SO_RCVBUF);
}
@Override
public SocketAddress getRemoteSocketAddress() {
return ch.remoteAddress();
}
@Override
public boolean getReuseAddress() throws SocketException {
return ch.config().getOption(ChannelOption.SO_REUSEADDR);
}
@Override
public synchronized int getSendBufferSize() throws SocketException {
return ch.config().getOption(ChannelOption.SO_SNDBUF);
}
@Override
public int getSoLinger() throws SocketException {
return ch.config().getOption(ChannelOption.SO_LINGER);
}
@Override
public synchronized int getSoTimeout() throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public boolean getTcpNoDelay() throws SocketException {
return ch.config().getOption(ChannelOption.TCP_NODELAY);
}
@Override
public int getTrafficClass() throws SocketException {
return ch.config().getOption(ChannelOption.IP_TOS);
}
@Override
public int hashCode() {
return ch.hashCode();
}
@Override
public boolean isBound() {
return ch.localAddress() != null;
}
@Override
public boolean isClosed() {
return !ch.isOpen();
}
@Override
public boolean isConnected() {
return ch.isActive();
}
@Override
public boolean isInputShutdown() {
return ch.isInputShutdown();
}
@Override
public boolean isOutputShutdown() {
return ch.isOutputShutdown();
}
@Override
public void sendUrgentData(int data) throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setKeepAlive(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.SO_KEEPALIVE, on);
}
@Override
public void setOOBInline(boolean on) throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public synchronized void setReceiveBufferSize(int size) throws SocketException {
ch.config().setOption(ChannelOption.SO_RCVBUF, size);
}
@Override
public void setReuseAddress(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.SO_REUSEADDR, on);
}
@Override
public synchronized void setSendBufferSize(int size) throws SocketException {
ch.config().setOption(ChannelOption.SO_SNDBUF, size);
}
@Override
public void setSoLinger(boolean on, int linger) throws SocketException {
ch.config().setOption(ChannelOption.SO_LINGER, linger);
}
@Override
public synchronized void setSoTimeout(int timeout) throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setTcpNoDelay(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.TCP_NODELAY, on);
}
@Override
public void setTrafficClass(int tc) throws SocketException {
ch.config().setOption(ChannelOption.IP_TOS, tc);
}
@Override
public void shutdownInput() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void shutdownOutput() throws IOException {
ch.shutdownOutput().syncUninterruptibly();
}
@Override
public String toString() {
return ch.toString();
}
}
@@ -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,244 +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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
/**
* An implicitly sorted array list that preserves insertion order and maintains duplicates.
* @param <T> - type of the elements in the list.
*/
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Collection<T> {
// Prevent reordering
private volatile List<T> list;
/**
* Construct an empty sorted array.
*/
public SortedCopyOnWriteArray() {
list = new ArrayList<T>();
}
/**
* Create a sorted array from the given list. The elements will be automatically sorted.
* @param wrapped - the collection whose elements are to be placed into the list.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped) {
this.list = new ArrayList<T>(wrapped);
}
/**
* Create a sorted array from the given list.
* @param wrapped - the collection whose elements are to be placed into the list.
* @param sort - TRUE to automatically sort the collection, FALSE if it is already sorted.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped, boolean sort) {
this.list = new ArrayList<T>(wrapped);
if (sort) {
Collections.sort(list);
}
}
/**
* Inserts the given element in the proper location.
* @param value - element to insert.
*/
@Override
public synchronized boolean add(T value) {
// We use NULL as a special marker, so we don't allow it
if (value == null)
throw new IllegalArgumentException("value cannot be NULL");
List<T> copy = new ArrayList<T>();
for (T element : list) {
// If the value is now greater than the current element, it should be placed right before it
if (value != null && value.compareTo(element) < 0) {
copy.add(value);
value = null;
}
copy.add(element);
}
// Don't forget to add it
if (value != null)
copy.add(value);
list = copy;
return true;
}
@Override
public synchronized boolean addAll(Collection<? extends T> values) {
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
// Insert the new content and sort it
copy.addAll(list);
copy.addAll(values);
Collections.sort(copy);
list = copy;
return true;
}
/**
* Removes from the list by making a new list with every element except the one given.
* <p>
* Objects will be compared using the given objects equals() method.
* @param value - value to remove.
*/
@Override
public synchronized boolean remove(Object value) {
List<T> copy = new ArrayList<T>();
boolean result = false;
// Note that there's not much to be gained from using BinarySearch, as we
// have to copy (and thus read) the entire list regardless.
// Copy every element except the one given to us.
for (T element : list) {
if (!Objects.equal(value, element)) {
copy.add(element);
} else {
result = true;
}
}
list = copy;
return result;
}
@Override
public boolean removeAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
copy.removeAll(values);
list = copy;
return true;
}
@Override
public boolean retainAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
copy.removeAll(values);
list = copy;
return true;
}
/**
* Removes from the list by making a copy of every element except the one with the given index.
* @param index - index of the element to remove.
*/
public synchronized void remove(int index) {
List<T> copy = new ArrayList<T>(list);
copy.remove(index);
list = copy;
}
/**
* Retrieves an element by index.
* @param index - index of element to retrieve.
* @return The element at the given location.
*/
public T get(int index) {
return list.get(index);
}
/**
* Retrieve the size of the list.
* @return Size of the list.
*/
public int size() {
return list.size();
}
/**
* Retrieves an iterator over the elements in the given list.
* Warning: No not attempt to remove elements using the iterator.
*/
public Iterator<T> iterator() {
return Iterables.unmodifiableIterable(list).iterator();
}
@Override
public void clear() {
list = new ArrayList<T>();
}
@Override
public boolean contains(Object value) {
return list.contains(value);
}
@Override
public boolean containsAll(Collection<?> values) {
return list.containsAll(values);
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public Object[] toArray() {
return list.toArray();
}
@SuppressWarnings("hiding")
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public String toString() {
return list.toString();
}
}
@@ -1,96 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.events;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
/**
* Represents a listener that is notified of every sent and received packet.
*
* @author Kristian
*/
public abstract class MonitorAdapter implements PacketListener {
private Plugin plugin;
private ListeningWhitelist sending = ListeningWhitelist.EMPTY_WHITELIST;
private ListeningWhitelist receiving = ListeningWhitelist.EMPTY_WHITELIST;
public MonitorAdapter(Plugin plugin, ConnectionSide side) {
initialize(plugin, side, getLogger(plugin));
}
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
initialize(plugin, side, logger);
}
@SuppressWarnings("deprecation")
private void initialize(Plugin plugin, ConnectionSide side, Logger logger) {
this.plugin = plugin;
// Recover in case something goes wrong
try {
if (side.isForServer())
this.sending = ListeningWhitelist.newBuilder().monitor().types(PacketRegistry.getServerPacketTypes()).gamePhaseBoth().build();
if (side.isForClient())
this.receiving = ListeningWhitelist.newBuilder().monitor().types(PacketRegistry.getClientPacketTypes()).gamePhaseBoth().build();
} catch (FieldAccessException e) {
if (side.isForServer())
this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getRegistry().values(), GamePhase.BOTH);
if (side.isForClient())
this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getRegistry().values(), GamePhase.BOTH);
logger.log(Level.WARNING, "Defaulting to 1.3 packets.", e);
}
}
/**
* Retrieve a logger, even if we're running in a CraftBukkit version that doesn't support it.
* @param plugin - the plugin to retrieve.
* @return The logger.
*/
private Logger getLogger(Plugin plugin) {
try {
return plugin.getLogger();
} catch (NoSuchMethodError e) {
return Logger.getLogger("Minecraft");
}
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sending;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receiving;
}
@Override
public Plugin getPlugin() {
return plugin;
}
}
@@ -1,421 +0,0 @@
package com.comphenix.protocol.events;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import javax.annotation.Nonnull;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.utility.ByteBufferInputStream;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
/**
* Marker containing the serialized packet data seen from the network,
* or output handlers that will serialize the current packet.
*
* @author Kristian
*/
public abstract class NetworkMarker {
public static class EmptyBufferMarker extends NetworkMarker {
public EmptyBufferMarker(@Nonnull ConnectionSide side) {
super(side, (byte[]) null, null);
}
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
throw new IllegalStateException("Buffer is empty.");
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
@Override
protected DataInputStream addHeader(DataInputStream input, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
}
// Custom network handler
private PriorityQueue<PacketOutputHandler> outputHandlers;
// Post listeners
private List<PacketPostListener> postListeners;
// Post packets
private List<ScheduledPacket> scheduledPackets;
// The input buffer
private ByteBuffer inputBuffer;
private final ConnectionSide side;
private final PacketType type;
// Cache serializer too
private StreamSerializer serializer;
/**
* Construct a new network marker.
* @param side - which side this marker belongs to.
* @param inputBuffer - the read serialized packet data.
* @param type - packet type
*/
public NetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer, PacketType type) {
this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
this.inputBuffer = inputBuffer;
this.type = type;
}
/**
* Construct a new network marker.
* <p>
* The input buffer is only non-null for client-side packets.
* @param side - which side this marker belongs to.
* @param inputBuffer - the read serialized packet data.
* @param type - packet type
*/
public NetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer, PacketType type) {
this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
this.type = type;
if (inputBuffer != null) {
this.inputBuffer = ByteBuffer.wrap(inputBuffer);
}
}
/**
* Retrieve whether or not this marker belongs to a client or a server side packet.
* @return The side the parent packet belongs to.
*/
public ConnectionSide getSide() {
return side;
}
/**
* Retrieve a utility class for serializing and deserializing Minecraft objects.
* @return Serialization utility class.
*/
public StreamSerializer getSerializer() {
if (serializer == null)
serializer = new StreamSerializer();
return serializer;
}
/**
* Retrieve the serialized packet data (excluding the header by default) from the network input stream.
* <p>
* The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}.
* <p>
* It returns NULL if the packet was transmitted by a plugin locally.
* @return A byte buffer containing the raw packet data read from the network.
*/
public ByteBuffer getInputBuffer() {
return getInputBuffer(true);
}
/**
* Retrieve the serialized packet data from the network input stream.
* <p>
* The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}.
* <p>
* It returns NULL if the packet was transmitted by a plugin locally.
* @param excludeHeader - whether or not to exclude the packet ID header.
* @return A byte buffer containing the raw packet data read from the network.
*/
public ByteBuffer getInputBuffer(boolean excludeHeader) {
if (side.isForServer())
throw new IllegalStateException("Server-side packets have no input buffer.");
if (inputBuffer != null) {
ByteBuffer result = inputBuffer.asReadOnlyBuffer();
try {
if (excludeHeader)
result = skipHeader(result);
else
result = addHeader(result, type);
} catch (IOException e) {
throw new RuntimeException("Cannot skip packet header.", e);
}
return result;
}
return null;
}
/**
* Retrieve the serialized packet data (excluding the header by default) as an input stream.
* <p>
* The data is exactly the same as in {@link #getInputBuffer()}.
* @see #getInputBuffer()
* @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally.
*/
public DataInputStream getInputStream() {
return getInputStream(true);
}
/**
* Retrieve the serialized packet data as an input stream.
* <p>
* The data is exactly the same as in {@link #getInputBuffer()}.
* @see #getInputBuffer()
* @param excludeHeader - whether or not to exclude the packet ID header.
* @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally.
*/
@SuppressWarnings("resource")
public DataInputStream getInputStream(boolean excludeHeader) {
if (side.isForServer())
throw new IllegalStateException("Server-side packets have no input buffer.");
if (inputBuffer == null)
return null;
DataInputStream input = new DataInputStream(
new ByteArrayInputStream(inputBuffer.array())
);
try {
if (excludeHeader)
input = skipHeader(input);
else
input = addHeader(input, type);
} catch (IOException e) {
throw new RuntimeException("Cannot skip packet header.", e);
}
return input;
}
/**
* Whether or not the output handlers have to write a packet header.
* @return TRUE if they do, FALSE otherwise.
*/
public boolean requireOutputHeader() {
return MinecraftReflection.isUsingNetty();
}
/**
* Enqueue the given output handler for managing how the current packet will be written to the network stream.
* <p>
* Note that output handlers are not serialized, as most consumers will probably implement them using anonymous classes.
* It is not safe to serialize anonymous classes, as their name depend on the order in which they are declared in the parent class.
* <p>
* This can only be invoked on server side packet events.
* @param handler - the handler that will take part in serializing the packet.
* @return TRUE if it was added, FALSE if it has already been added.
*/
public boolean addOutputHandler(@Nonnull PacketOutputHandler handler) {
checkServerSide();
Preconditions.checkNotNull(handler, "handler cannot be NULL.");
// Lazy initialization - it's imperative that we save space and time here
if (outputHandlers == null) {
outputHandlers = new PriorityQueue<PacketOutputHandler>(10, new Comparator<PacketOutputHandler>() {
@Override
public int compare(PacketOutputHandler o1, PacketOutputHandler o2) {
return Ints.compare(o1.getPriority().getSlot(), o2.getPriority().getSlot());
}
});
}
return outputHandlers.add(handler);
}
/**
* Remove a given output handler from the serialization queue.
* <p>
* This can only be invoked on server side packet events.
* @param handler - the handler to remove.
* @return TRUE if the handler was removed, FALSE otherwise.
*/
public boolean removeOutputHandler(@Nonnull PacketOutputHandler handler) {
checkServerSide();
Preconditions.checkNotNull(handler, "handler cannot be NULL.");
if (outputHandlers != null) {
return outputHandlers.remove(handler);
}
return false;
}
/**
* Retrieve every registered output handler in no particular order.
* @return Every registered output handler.
*/
@Nonnull
public Collection<PacketOutputHandler> getOutputHandlers() {
if (outputHandlers != null) {
return outputHandlers;
} else {
return Collections.emptyList();
}
}
/**
* Add a listener that is invoked after a packet has been successfully sent to the client, or received
* by the server.
* <p>
* Received packets are not guarenteed to have been fully processed, but packets passed
* to {@link ProtocolManager#recieveClientPacket(Player, PacketContainer)} will be processed after the
* current packet event.
* <p>
* Note that post listeners will be executed asynchronously off the main thread. They are not executed
* in any defined order.
* @param listener - the listener that will be invoked.
* @return TRUE if it was added.
*/
public boolean addPostListener(PacketPostListener listener) {
if (postListeners == null)
postListeners = Lists.newArrayList();
return postListeners.add(listener);
}
/**
* Remove the first instance of the given listener.
* @param listener - listener to remove.
* @return TRUE if it was removed, FALSE otherwise.
*/
public boolean removePostListener(PacketPostListener listener) {
if (postListeners != null) {
return postListeners.remove(listener);
}
return false;
}
/**
* Retrieve an immutable view of all the listeners that will be invoked once the packet has been sent or received.
* @return Every post packet listener. Never NULL.
*/
public List<PacketPostListener> getPostListeners() {
return postListeners != null ? Collections.unmodifiableList(postListeners) : Collections.<PacketPostListener>emptyList();
}
/**
* Retrieve a list of packets that will be schedule (in-order) when the current packet has been successfully transmitted.
* <p>
* This list is modifiable.
* @return List of packets that will be scheduled.
*/
public List<ScheduledPacket> getScheduledPackets() {
if (scheduledPackets == null)
scheduledPackets = Lists.newArrayList();
return scheduledPackets;
}
/**
* Ensure that the packet event is server side.
*/
private void checkServerSide() {
if (side.isForClient()) {
throw new IllegalStateException("Must be a server side packet.");
}
}
/**
* Return a byte buffer without the header in the current packet.
* <p>
* It's safe to modify the position of the buffer.
* @param buffer - a read-only byte source.
* @return A byte buffer without the header in the current packet.
* @throws IOException If integer reading fails
*/
protected ByteBuffer skipHeader(ByteBuffer buffer) throws IOException {
skipHeader(new DataInputStream(new ByteBufferInputStream(buffer)));
return buffer;
}
/**
* Return an input stream without the header in the current packet.
* <p>
* It's safe to modify the input stream.
* @param input - input stream
* @return An input stream without the header
* @throws IOException If integer reading fails
*/
protected abstract DataInputStream skipHeader(DataInputStream input) throws IOException;
/**
* Return the byte buffer prepended with the packet header.
* @param buffer - the read-only byte buffer.
* @param type - the current packet.
* @return The byte buffer.
*/
protected abstract ByteBuffer addHeader(ByteBuffer buffer, PacketType type);
/**
* Return the input stream prepended with the packet header.
* @param input - the input stream.
* @param type - the current packet.
* @return The byte buffer.
*/
protected abstract DataInputStream addHeader(DataInputStream input, PacketType type);
/**
* Determine if the given marker has any output handlers.
* @param marker - the marker to check.
* @return TRUE if it does, FALSE otherwise.
*/
public static boolean hasOutputHandlers(NetworkMarker marker) {
return marker != null && !marker.getOutputHandlers().isEmpty();
}
/**
* Determine if the given marker has any post listeners.
* @param marker - the marker to check.
* @return TRUE if it does, FALSE otherwise.
*/
public static boolean hasPostListeners(NetworkMarker marker) {
return marker != null && !marker.getPostListeners().isEmpty();
}
/**
* Retrieve the byte buffer stored in the given marker.
* @param marker - the marker.
* @return The byte buffer, or NULL if not found.
*/
public static byte[] getByteBuffer(NetworkMarker marker) {
if (marker != null) {
ByteBuffer buffer = marker.getInputBuffer();
if (buffer != null) {
byte[] data = new byte[buffer.remaining()];
buffer.get(data, 0, data.length);
return data;
}
}
return null;
}
/**
* Retrieve the network marker of a particular event without creating it.
* <p>
* This is an internal method that should not be used by API users.
* @param event - the event.
* @return The network marker.
*/
public static NetworkMarker getNetworkMarker(PacketEvent event) {
return event.networkMarker;
}
/**
* Retrieve the scheduled packets of a particular network marker without constructing the list.
* <p>
* This is an internal method that should not be used by API users.
* @param marker - the marker.
* @return The list, or NULL if not found or initialized.
*/
public static List<ScheduledPacket> readScheduledPackets(NetworkMarker marker) {
return marker.scheduledPackets;
}
}
File diff suppressed because it is too large. Load diff
@@ -1,243 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.events;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.Player;
import com.comphenix.protocol.utility.EnhancerFactory;
/**
* Represents a player object that can be serialized by Java.
*
* @author Kristian
*/
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = -2728976288470282810L;
private transient Location bedSpawnLocation;
// Relevant data about an offline player
private String name;
private UUID uuid;
private long firstPlayed;
private long lastPlayed;
private boolean operator;
private boolean banned;
private boolean playedBefore;
private boolean online;
private boolean whitelisted;
// Proxy helper
private static Map<String, Method> lookup = new ConcurrentHashMap<String, Method>();
/**
* Constructor used by serialization.
*/
public SerializedOfflinePlayer() {
// Do nothing
}
/**
* Initialize this serializable offline player from another player.
* @param offline - another player.
*/
public SerializedOfflinePlayer(OfflinePlayer offline) {
this.name = offline.getName();
this.uuid = offline.getUniqueId();
this.firstPlayed = offline.getFirstPlayed();
this.lastPlayed = offline.getLastPlayed();
this.operator = offline.isOp();
this.banned = offline.isBanned();
this.playedBefore = offline.hasPlayedBefore();
this.online = offline.isOnline();
this.whitelisted = offline.isWhitelisted();
}
@Override
public boolean isOp() {
return operator;
}
@Override
public void setOp(boolean operator) {
this.operator = operator;
}
@Override
public Map<String, Object> serialize() {
throw new UnsupportedOperationException();
}
@Override
public Location getBedSpawnLocation() {
return bedSpawnLocation;
}
@Override
public long getFirstPlayed() {
return firstPlayed;
}
@Override
public long getLastPlayed() {
return lastPlayed;
}
@Override
public UUID getUniqueId() {
return uuid;
}
@Override
public String getName() {
return name;
}
@Override
public boolean hasPlayedBefore() {
return playedBefore;
}
@Override
public boolean isBanned() {
return banned;
}
@Override
public void setBanned(boolean banned) {
this.banned = banned;
}
@Override
public boolean isOnline() {
return online;
}
@Override
public boolean isWhitelisted() {
return whitelisted;
}
@Override
public void setWhitelisted(boolean whitelisted) {
this.whitelisted = whitelisted;
}
private void writeObject(ObjectOutputStream output) throws IOException {
output.defaultWriteObject();
// Serialize the bed spawn location
output.writeUTF(bedSpawnLocation.getWorld().getName());
output.writeDouble(bedSpawnLocation.getX());
output.writeDouble(bedSpawnLocation.getY());
output.writeDouble(bedSpawnLocation.getZ());
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
input.defaultReadObject();
// Well, this is a problem
bedSpawnLocation = new Location(
getWorld(input.readUTF()),
input.readDouble(),
input.readDouble(),
input.readDouble()
);
}
private World getWorld(String name) {
try {
// Try to get the world at least
return Bukkit.getServer().getWorld(name);
} catch (Exception e) {
// Screw it
return null;
}
}
@Override
public Player getPlayer() {
try {
// Try to get the real player underneath
return Bukkit.getServer().getPlayerExact(name);
} catch (Exception e) {
return getProxyPlayer();
}
}
/**
* Retrieve a player object that implements OfflinePlayer by refering to this object.
* <p>
* All other methods cause an exception.
* @return Proxy object.
*/
public Player getProxyPlayer() {
// Remember to initialize the method filter
if (lookup.size() == 0) {
// Add all public methods
for (Method method : OfflinePlayer.class.getMethods()) {
lookup.put(method.getName(), method);
}
}
// MORE CGLIB magic!
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
ex.setSuperclass(Player.class);
ex.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// There's no overloaded methods, so we don't care
Method offlineMethod = lookup.get(method.getName());
// Ignore all other methods
if (offlineMethod == null) {
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players.");
}
// Invoke our on method
return offlineMethod.invoke(SerializedOfflinePlayer.this, args);
}
});
return (Player) ex.create();
}
}
@@ -1,512 +0,0 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* A protocol manager that delays all packet listener registrations and unregistrations until
* an underlying protocol manager can be constructed.
*
* @author Kristian
*/
public class DelayedPacketManager implements InternalManager {
// Registering packet IDs that are not supported
public static final ReportType REPORT_CANNOT_SEND_QUEUED_PACKET = new ReportType("Cannot send queued packet %s.");
public static final ReportType REPORT_CANNOT_SEND_QUEUED_WIRE_PACKET = new ReportType("Cannot send queued wire packet %s.");
public static final ReportType REPORT_CANNOT_REGISTER_QUEUED_LISTENER = new ReportType("Cannot register queued listener %s.");
private volatile InternalManager delegate;
// Queued actions
private final List<Runnable> queuedActions = Collections.synchronizedList(Lists.<Runnable>newArrayList());
private final List<PacketListener> queuedListeners = Collections.synchronizedList(Lists.<PacketListener>newArrayList());
private AsynchronousManager asyncManager;
private ErrorReporter reporter;
// The current hook
private PlayerInjectHooks hook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// If we have been closed
private boolean closed;
private boolean debug;
// Queued registration
private PluginManager queuedManager;
private Plugin queuedPlugin;
private MinecraftVersion version;
public DelayedPacketManager(@Nonnull ErrorReporter reporter, @Nonnull MinecraftVersion version) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL.");
Preconditions.checkNotNull(version, "version cannot be NULL.");
this.reporter = reporter;
this.version = version;
}
/**
* Retrieve the underlying protocol manager.
* @return The underlying manager.
*/
public InternalManager getDelegate() {
return delegate;
}
@Override
public int getProtocolVersion(Player player) {
if (delegate != null)
return delegate.getProtocolVersion(player);
else
return Integer.MIN_VALUE;
}
@Override
public MinecraftVersion getMinecraftVersion() {
if (delegate != null)
return delegate.getMinecraftVersion();
else
return version;
}
/**
* Update the delegate to the underlying manager.
* <p>
* This will prompt this packet manager to immediately transmit and
* register all queued packets an listeners.
* @param delegate - delegate to the new manager.
*/
protected void setDelegate(InternalManager delegate) {
this.delegate = delegate;
if (delegate != null) {
// Update the hook if needed
if (!Objects.equal(delegate.getPlayerHook(), hook)) {
delegate.setPlayerHook(hook);
}
// Register events as well
if (queuedManager != null && queuedPlugin != null) {
delegate.registerEvents(queuedManager, queuedPlugin);
}
// And update the debug mode
delegate.setDebug(debug);
// Add any pending listeners
synchronized (queuedListeners) {
for (PacketListener listener : queuedListeners) {
try {
delegate.addPacketListener(listener);
} catch (IllegalArgumentException e) {
// Inform about this plugin error
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_QUEUED_LISTENER).
callerParam(delegate).messageParam(listener).error(e));
}
}
}
// Execute any delayed actions
synchronized (queuedActions) {
for (Runnable action : queuedActions) {
action.run();
}
}
// Don't keep this around anymore
queuedListeners.clear();
queuedActions.clear();
}
}
private Runnable queuedAddPacket(final ConnectionSide side, final Player player, final PacketContainer packet,
final NetworkMarker marker, final boolean filtered) {
return new Runnable() {
@Override
public void run() {
try {
// Attempt to send it now
switch (side) {
case CLIENT_SIDE:
delegate.recieveClientPacket(player, packet, marker, filtered);
break;
case SERVER_SIDE:
delegate.sendServerPacket(player, packet, marker, filtered);
break;
default:
throw new IllegalArgumentException("side cannot be " + side);
}
} catch (Exception e) {
// Inform about this plugin error
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_PACKET).
callerParam(delegate).messageParam(packet).error(e));
}
}
};
}
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
this.hook = playerHook;
}
@Override
public PlayerInjectHooks getPlayerHook() {
return hook;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet) throws InvocationTargetException {
sendServerPacket(receiver, packet, null, true);
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters) throws InvocationTargetException {
sendServerPacket(receiver, packet, null, filters);
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
if (delegate != null) {
delegate.sendServerPacket(receiver, packet, marker, filters);
} else {
queuedActions.add(queuedAddPacket(ConnectionSide.SERVER_SIDE, receiver, packet, marker, filters));
}
}
@Override
public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException {
WirePacket packet = new WirePacket(id, bytes);
sendWirePacket(receiver, packet);
}
@Override
public void sendWirePacket(final Player receiver, final WirePacket packet) throws InvocationTargetException {
if (delegate != null) {
delegate.sendWirePacket(receiver, packet);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
try {
delegate.sendWirePacket(receiver, packet);
} catch (Throwable ex) {
// Inform about this plugin error
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_WIRE_PACKET)
.callerParam(delegate)
.messageParam(packet)
.error(ex));
}
}
});
}
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, null, true);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, null, filters);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) throws IllegalAccessException, InvocationTargetException {
if (delegate != null) {
delegate.recieveClientPacket(sender, packet, marker, filters);
} else {
queuedActions.add(queuedAddPacket(ConnectionSide.CLIENT_SIDE, sender, packet, marker, filters));
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet, final Entity entity, final boolean includeTracker) {
if (delegate != null) {
delegate.broadcastServerPacket(packet, entity, includeTracker);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet, entity, includeTracker);
}
});
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet, final Location origin, final int maxObserverDistance) {
if (delegate != null) {
delegate.broadcastServerPacket(packet, origin, maxObserverDistance);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet, origin, maxObserverDistance);
}
});
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet) {
if (delegate != null) {
delegate.broadcastServerPacket(packet);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet);
}
});
}
}
@Override
public ImmutableSet<PacketListener> getPacketListeners() {
if (delegate != null)
return delegate.getPacketListeners();
else
return ImmutableSet.copyOf(queuedListeners);
}
@Override
public void addPacketListener(PacketListener listener) {
if (delegate != null)
delegate.addPacketListener(listener);
else
queuedListeners.add(listener);
}
@Override
public void removePacketListener(PacketListener listener) {
if (delegate != null)
delegate.removePacketListener(listener);
else
queuedListeners.remove(listener);
}
@Override
public void removePacketListeners(Plugin plugin) {
if (delegate != null) {
delegate.removePacketListeners(plugin);
} else {
for (Iterator<PacketListener> it = queuedListeners.iterator(); it.hasNext(); ) {
// Remove listeners of the same plugin
if (Objects.equal(it.next().getPlugin(), plugin)) {
it.remove();
}
}
}
}
@Override
@Deprecated
public PacketContainer createPacket(int id) {
if (delegate != null)
return delegate.createPacket(id);
return createPacket(id, true);
}
@Override
@Deprecated
public PacketContainer createPacket(int id, boolean forceDefaults) {
if (delegate != null) {
return delegate.createPacket(id);
} else {
// Fallback implementation
PacketContainer packet = new PacketContainer(id);
// Use any default values if possible
if (forceDefaults) {
try {
packet.getModifier().writeDefaults();
} catch (FieldAccessException e) {
throw new RuntimeException("Security exception.", e);
}
}
return packet;
}
}
@SuppressWarnings("deprecation")
@Override
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
if (delegate != null)
return delegate.createPacketConstructor(id, arguments);
else
return PacketConstructor.DEFAULT.withPacket(id, arguments);
}
@Override
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments) {
if (delegate != null)
return delegate.createPacketConstructor(type, arguments);
else
return PacketConstructor.DEFAULT.withPacket(type, arguments);
}
@Override
@Deprecated
public Set<Integer> getSendingFilters() {
if (delegate != null) {
return delegate.getSendingFilters();
} else {
// Linear scan is fast enough here
Set<Integer> sending = Sets.newHashSet();
for (PacketListener listener : queuedListeners) {
sending.addAll(listener.getSendingWhitelist().getWhitelist());
}
return sending;
}
}
@Override
@Deprecated
public Set<Integer> getReceivingFilters() {
if (delegate != null) {
return delegate.getReceivingFilters();
} else {
Set<Integer> recieving = Sets.newHashSet();
for (PacketListener listener : queuedListeners) {
recieving.addAll(listener.getReceivingWhitelist().getWhitelist());
}
return recieving;
}
}
@Override
public PacketContainer createPacket(PacketType type) {
return createPacket(type.getLegacyId());
}
@Override
public PacketContainer createPacket(PacketType type, boolean forceDefaults) {
return createPacket(type.getLegacyId(), forceDefaults);
}
@Override
public Set<PacketType> getSendingFilterTypes() {
return PacketRegistry.toPacketTypes(getSendingFilters(), Sender.SERVER);
}
@Override
public Set<PacketType> getReceivingFilterTypes() {
return PacketRegistry.toPacketTypes(getReceivingFilters(), Sender.CLIENT);
}
@Override
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
if (delegate != null)
delegate.updateEntity(entity, observers);
else
EntityUtilities.updateEntity(entity, observers);
}
@Override
public Entity getEntityFromID(World container, int id) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityFromID(container, id);
else
return EntityUtilities.getEntityFromID(container, id);
}
@Override
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityTrackers(entity);
else
return EntityUtilities.getEntityTrackers(entity);
}
@Override
public boolean isClosed() {
return closed || (delegate != null && delegate.isClosed());
}
@Override
public AsynchronousManager getAsynchronousManager() {
if (delegate != null)
return delegate.getAsynchronousManager();
else
return asyncManager;
}
@Override
public boolean isDebug() {
return debug;
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;
if (delegate != null) {
delegate.setDebug(debug);
}
}
/**
* Update the asynchronous manager. This must be set.
* @param asyncManager - the asynchronous manager.
*/
public void setAsynchronousManager(AsynchronousManager asyncManager) {
this.asyncManager = asyncManager;
}
@Override
public void registerEvents(PluginManager manager, Plugin plugin) {
if (delegate != null) {
delegate.registerEvents(manager, plugin);
} else {
queuedManager = manager;
queuedPlugin = plugin;
}
}
@Override
public void close() {
if (delegate != null)
delegate.close();
closed = true;
}
}
@@ -1,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();
}
}
@@ -1,264 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
import com.google.common.collect.Lists;
/**
* Used to perform certain operations on entities.
*
* @author Kristian
*/
class EntityUtilities {
private static Field entityTrackerField;
private static Field trackedEntitiesField;
private static Field trackedPlayersField;
private static Field trackerField;
private static Method scanPlayersMethod;
/*
* While this function may look pretty bad, it's essentially just a reflection-warped
* version of the following:
*
* @SuppressWarnings("unchecked")
* public static void updateEntity2(Entity entity, List<Player> observers) {
*
* World world = entity.getWorld();
* WorldServer worldServer = ((CraftWorld) world).getHandle();
*
* EntityTracker tracker = worldServer.tracker;
* EntityTrackerEntry entry = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
*
* List<EntityPlayer> nmsPlayers = getNmsPlayers(observers);
*
* entry.trackedPlayers.removeAll(nmsPlayers);
* entry.scanPlayers(nmsPlayers);
* }
*
* private static List<EntityPlayer> getNmsPlayers(List<Player> players) {
* List<EntityPlayer> nsmPlayers = new ArrayList<EntityPlayer>();
*
* for (Player bukkitPlayer : players) {
* CraftPlayer craftPlayer = (CraftPlayer) bukkitPlayer;
* nsmPlayers.add(craftPlayer.getHandle());
* }
*
* return nsmPlayers;
* }
*
*/
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
try {
//EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
if (trackedPlayersField == null) {
// This one is fairly easy
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
}
// Phew, finally there.
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
List<Object> nmsPlayers = unwrapBukkit(observers);
// trackEntity.trackedPlayers.clear();
trackedPlayers.removeAll(nmsPlayers);
// We have to rely on a NAME once again. Damn it.
if (scanPlayersMethod == null) {
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
}
//trackEntity.scanPlayers(server.players);
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
} catch (IllegalArgumentException e) {
throw e;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occurred in Minecraft.", e);
} catch (SecurityException e) {
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
} catch (NoSuchMethodException e) {
throw new FieldAccessException("Cannot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
}
}
/**
* Retrieve every client that is receiving information about a given entity.
* @param entity - the entity that is being tracked.
* @return Every client/player that is tracking the given entity.
* @throws FieldAccessException If reflection failed.
*/
public static List<Player> getEntityTrackers(Entity entity) {
try {
List<Player> result = new ArrayList<Player>();
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
if (trackerEntry == null) {
throw new IllegalArgumentException("Cannot find entity trackers for " + entity +
(entity.isDead() ? " - entity is dead." : "."));
}
if (trackedPlayersField == null) {
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
}
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
// Wrap every player - we also ensure that the underlying tracker list is immutable
for (Object tracker : trackedPlayers) {
if (MinecraftReflection.isMinecraftPlayer(tracker)) {
result.add((Player) MinecraftReflection.getBukkitEntity(tracker));
}
}
return result;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Security limitation prevented access to the list of tracked players.", e);
}
}
/**
* Retrieve the entity tracker entry given a ID.
* @param world - world server.
* @param entityID - entity ID.
* @return The entity tracker entry.
* @throws FieldAccessException
*/
private static Object getEntityTrackerEntry(World world, int entityID) throws FieldAccessException, IllegalArgumentException {
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
Object worldServer = unwrapper.unwrapItem(world);
if (entityTrackerField == null)
entityTrackerField = FuzzyReflection.fromObject(worldServer).
getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass());
// Get the tracker
Object tracker = null;
try {
tracker = FieldUtils.readField(entityTrackerField, worldServer, false);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'tracker' field due to security limitations.", e);
}
if (trackedEntitiesField == null) {
@SuppressWarnings("rawtypes")
Set<Class> ignoredTypes = new HashSet<Class>();
// Well, this is more difficult. But we're looking for a Minecraft object that is not
// created by the constructor(s).
for (Constructor<?> constructor : tracker.getClass().getConstructors()) {
for (Class<?> type : constructor.getParameterTypes()) {
ignoredTypes.add(type);
}
}
// The Minecraft field that's NOT filled in by the constructor
trackedEntitiesField = FuzzyReflection.fromObject(tracker, true).
getFieldByType(MinecraftReflection.getMinecraftObjectRegex(), ignoredTypes);
}
// Read the entity hashmap
Object trackedEntities = null;
try {
trackedEntities = FieldUtils.readField(trackedEntitiesField, tracker, true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'trackedEntities' field due to security limitations.", e);
}
return WrappedIntHashMap.fromHandle(trackedEntities).get(entityID);
}
/**
* Retrieve entity from a ID, even it it's newly created.
* @return The asssociated entity.
* @throws FieldAccessException Reflection error.
*/
public static Entity getEntityFromID(World world, int entityID) throws FieldAccessException {
try {
Object trackerEntry = getEntityTrackerEntry(world, entityID);
Object tracker = null;
// Handle NULL cases
if (trackerEntry != null) {
if (trackerField == null) {
try {
trackerField = trackerEntry.getClass().getField("tracker");
} catch (NoSuchFieldException e) {
// Assume it's the first public entity field then
trackerField = FuzzyReflection.fromObject(trackerEntry).getFieldByType(
"tracker", MinecraftReflection.getEntityClass());
}
}
tracker = FieldUtils.readField(trackerField, trackerEntry, true);
}
// If the tracker is NULL, we'll just assume this entity doesn't exist
if (tracker != null)
return (Entity) MinecraftReflection.getBukkitEntity(tracker);
else
return null;
} catch (Exception e) {
throw new FieldAccessException("Cannot find entity from ID " + entityID + ".", e);
}
}
private static List<Object> unwrapBukkit(List<Player> players) {
List<Object> output = Lists.newArrayList();
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
// Get the NMS equivalent
for (Player player : players) {
Object result = unwrapper.unwrapItem(player);
if (result != null)
output.add(result);
else
throw new IllegalArgumentException("Cannot unwrap item " + player);
}
return output;
}
}
@@ -1,94 +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 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);
/**
* 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);
/**
* 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);
}
@@ -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 +0,0 @@
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;
/**
* Represents a processor for network markers.
* @author Kristian
*/
public class NetworkProcessor {
private 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 marker - the network marker, or NULL.
*/
public void invokePostEvent(PacketEvent event, NetworkMarker marker) {
if (marker == null)
return;
if (NetworkMarker.hasPostListeners(marker)) {
// Invoke every sent listener
for (PacketPostListener listener : marker.getPostListeners()) {
try {
listener.onPostEvent(event);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e);
}
}
}
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);
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
if (scheduled != null) {
for (ScheduledPacket packet : scheduled) {
packet.schedule(manager);
}
}
}
}
@@ -1,256 +0,0 @@
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;
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.
*/
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;
}
/**
* Retrieve a reference to the current ProtocolLib instance.
* @return ProtocolLib.
*/
public Plugin getLibrary() {
return 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;
}
/**
* Retrieve the error reporter.
* @return Error reporter.
*/
public ErrorReporter getReporter() {
return reporter;
}
/**
* Retrieve the asynchronous manager.
* <p>
* This is first constructed the {@link #build()} method.
* @return The asynchronous manager.
*/
public AsyncFilterManager getAsyncManager() {
return asyncManager;
}
/**
* Create a new packet filter manager.
* @return A new packet filter manager.
*/
public InternalManager build() {
if (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;
}
}
File diff suppressed because it is too large. Load diff
@@ -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));
}
}
@@ -1,175 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import 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.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Caches structure modifiers.
* @author Kristian
*/
public class StructureCache {
// Structure modifiers
private static ConcurrentMap<PacketType, StructureModifier<Object>> structureModifiers =
new ConcurrentHashMap<PacketType, StructureModifier<Object>>();
private static Set<PacketType> compiling = new HashSet<PacketType>();
/**
* 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));
}
/**
* 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) {
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);
}
/**
* 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);
}
/**
* 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.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(final PacketType type, boolean compile) {
StructureModifier<Object> result = structureModifiers.get(type);
// 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);
result = structureModifiers.putIfAbsent(type, value);
// We may end up creating multiple modifiers, but we'll agree on which to use
if (result == null) {
result = value;
}
}
// Automatically compile the structure modifier
if (compile && !(result instanceof CompiledStructureModifier)) {
// Compilation is many orders of magnitude slower than synchronization
synchronized (compiling) {
final BackgroundCompiler compiler = BackgroundCompiler.getInstance();
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);
}
}
}
return result;
}
}
@@ -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,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
*/
// TODO: Handle modifications to the BiMap
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() {
// See if the register is outdated
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,55 +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.compat.netty.WrappedByteBuf;
/**
* @author dmulloy2
*/
public class WirePacket {
private final int id;
private final byte[] bytes;
public WirePacket(int id, byte[] bytes) {
this.id = id;
this.bytes = bytes;
}
public int getId() {
return id;
}
public byte[] getBytes() {
return bytes;
}
public void writeId(WrappedByteBuf output) {
int i = id;
while ((i & -128) != 0) {
output.writeByte(i & 127 | 128);
i >>>= 7;
}
output.writeByte(i);
}
public void writeBytes(WrappedByteBuf output) {
output.writeBytes(bytes);
}
}
@@ -1,64 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Represents an input stream that stores every read block of bytes in another output stream.
*
* @author Kristian
*/
class CaptureInputStream extends FilterInputStream {
protected OutputStream out;
public CaptureInputStream(InputStream in, OutputStream out) {
super(in);
this.out = out;
}
@Override
public int read() throws IOException {
int value = super.read();
// Write the byte
if (value >= 0)
out.write(value);
return value;
}
@Override
public void close() throws IOException {
super.close();
out.close();
}
@Override
public int read(byte[] b) throws IOException {
int count = super.read(b);
if (count > 0 ) {
out.write(b, 0, count);
}
return count;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int count = super.read(b, off, len);
if (count > 0 ) {
out.write(b, off, count);
}
return count;
}
/**
* Retrieve the output stream that receives all the read information.
* @return The output stream everything is copied to.
*/
public OutputStream getOutputStream() {
return out;
}
}
@@ -1,154 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.io.DataOutput;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;
/**
* Retrieve a packet instance that has its write method intercepted.
* @author Kristian
*/
public class InterceptWritePacket {
public static final ReportType REPORT_CANNOT_FIND_WRITE_PACKET_METHOD = new ReportType("Cannot find write packet method in %s.");
public static final ReportType REPORT_CANNOT_CONSTRUCT_WRITE_PROXY = new ReportType("Cannot construct write proxy packet %s.");
/**
* Matches the readPacketData(DataInputStream) method in Packet.
*/
private static FuzzyMethodContract WRITE_PACKET = FuzzyMethodContract.newBuilder().
returnTypeVoid().
parameterDerivedOf(DataOutput.class).
parameterCount(1).
build();
private CallbackFilter filter;
private boolean writePacketIntercepted;
private ConcurrentMap<Integer, Class<?>> proxyClasses = Maps.newConcurrentMap();
private ErrorReporter reporter;
private WritePacketModifier modifierWrite;
private WritePacketModifier modifierRest;
public InterceptWritePacket(ErrorReporter reporter) {
this.reporter = reporter;
// Initialize modifiers
this.modifierWrite = new WritePacketModifier(reporter, true);
this.modifierRest = new WritePacketModifier(reporter, false);
}
// TODO: PacketId should probably do something...
private Class<?> createProxyClass() {
// Construct the proxy object
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
// Attempt to share callback filter
if (filter == null) {
filter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Skip methods defined in Object
if (WRITE_PACKET.isMatch(MethodInfo.fromMethod(method), null)) {
writePacketIntercepted = true;
return 0;
} else {
return 1;
}
}
};
}
// Subclass the generic packet class
ex.setSuperclass(MinecraftReflection.getPacketClass());
ex.setCallbackFilter(filter);
ex.setUseCache(false);
ex.setCallbackTypes( new Class[] { WritePacketModifier.class, WritePacketModifier.class });
Class<?> proxyClass = ex.createClass();
// Register write modifiers too
Enhancer.registerStaticCallbacks(proxyClass, new Callback[] { modifierWrite, modifierRest });
if (proxyClass != null) {
// Check that we found the read method
if (!writePacketIntercepted) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_FIND_WRITE_PACKET_METHOD).
messageParam(MinecraftReflection.getPacketClass()));
}
}
return proxyClass;
}
@SuppressWarnings("deprecation")
private Class<?> getProxyClass(int packetId) {
Class<?> stored = proxyClasses.get(packetId);
// Concurrent pattern
if (stored == null) {
final Class<?> created = createProxyClass();
stored = proxyClasses.putIfAbsent(packetId, created);
// We won!
if (stored == null) {
stored = created;
PacketRegistry.getPacketToID().put(stored, packetId);
}
}
return stored;
}
/**
* Construct a new instance of the proxy object.
* @param proxyObject - Object to construct proxy of
* @param event - Packet event
* @param marker - Network marker
* @return New instance of the proxy, or null if we failed.
*/
@SuppressWarnings("deprecation")
public Object constructProxy(Object proxyObject, PacketEvent event, NetworkMarker marker) {
Class<?> proxyClass = null;
try {
proxyClass = getProxyClass(event.getPacketID());
Object generated = proxyClass.newInstance();
modifierWrite.register(generated, proxyObject, event, marker);
modifierRest.register(generated, proxyObject, event, marker);
return generated;
} catch (Exception e) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_CONSTRUCT_WRITE_PROXY).
messageParam(proxyClass));
return null;
}
}
/**
* Invoked when the write packet proxy class should be removed.
*/
@SuppressWarnings("deprecation")
public void cleanup() {
// Remove all proxy classes from the registry
for (Class<?> stored : proxyClasses.values()) {
PacketRegistry.getPacketToID().remove(stored);
}
}
}
@@ -1,66 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ForwardingMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
public class InverseMaps {
private InverseMaps() {
// Not constructable
}
public static <K, V> Multimap<K, V> inverseMultimap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
final MapContainer container = new MapContainer(map);
return new ForwardingMultimap<K, V>() {
// The cached multimap
private Multimap<K, V> inverseMultimap;
@Override
protected Multimap<K, V> delegate() {
if (container.hasChanged()) {
inverseMultimap = HashMultimap.create();
// Construct the inverse map
for (Map.Entry<V, K> entry : map.entrySet()) {
if (filter.apply(entry)) {
inverseMultimap.put(entry.getValue(), entry.getKey());
}
}
container.setChanged(false);
}
return inverseMultimap;
}
};
}
public static <K, V> Map<K, V> inverseMap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
final MapContainer container = new MapContainer(map);
return new ForwardingMap<K, V>() {
// The cached map
private Map<K, V> inverseMap;
@Override
protected Map<K, V> delegate() {
if (container.hasChanged()) {
inverseMap = Maps.newHashMap();
// Construct the inverse map
for (Map.Entry<V, K> entry : map.entrySet()) {
if (filter.apply(entry)) {
inverseMap.put(entry.getValue(), entry.getKey());
}
}
container.setChanged(false);
}
return inverseMap;
}
};
}
}
@@ -1,43 +0,0 @@
package com.comphenix.protocol.injector.packet;
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.compat.guava.Guava;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.google.common.primitives.Bytes;
/**
* Represents a network marker for 1.6.4 and earlier.
* @author Kristian
*/
public class LegacyNetworkMarker extends NetworkMarker {
public LegacyNetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer, PacketType type) {
super(side, inputBuffer, type);
}
public LegacyNetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer, PacketType type) {
super(side, inputBuffer, type);
}
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
// This has already been done
return input;
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
return ByteBuffer.wrap(Bytes.concat(new byte[] { (byte) type.getLegacyId() }, buffer.array()));
}
@Override
protected DataInputStream addHeader(final DataInputStream input, final PacketType type) {
return Guava.addHeader(input, type);
}
}
@@ -1,334 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
import net.sf.cglib.proxy.Factory;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.TroveWrapper;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
@SuppressWarnings("rawtypes")
class LegacyPacketRegistry {
private static final int MIN_SERVER_PACKETS = 5;
private static final int MIN_CLIENT_PACKETS = 5;
// Fuzzy reflection
private FuzzyReflection packetRegistry;
// The packet class to packet ID translator
private Map<Class, Integer> packetToID;
// Packet IDs to classes, grouped by whether or not they're vanilla or custom defined
private Multimap<Integer, Class> customIdToPacket;
private Map<Integer, Class> vanillaIdToPacket;
// Whether or not certain packets are sent by the client or the server
private ImmutableSet<Integer> serverPackets;
private ImmutableSet<Integer> clientPackets;
// The underlying sets
private Set<Integer> serverPacketsRef;
private Set<Integer> clientPacketsRef;
// New proxy values
private Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
// Vanilla packets
private Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
/**
* Initialize the registry.
*/
@SuppressWarnings({ "unchecked" })
public void initialize() {
if (packetToID == null) {
try {
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalArgumentException e) {
// Spigot 1.2.5 MCPC workaround
try {
packetToID = getSpigotWrapper();
} catch (Exception e2) {
// Very bad indeed
throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2);
}
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
}
// Create the inverse maps
customIdToPacket = InverseMaps.inverseMultimap(packetToID, new Predicate<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> entry) {
return !MinecraftReflection.isMinecraftClass(entry.getKey());
}
});
// And the vanilla pack - here we assume a unique ID to class mapping
vanillaIdToPacket = InverseMaps.inverseMap(packetToID, new Predicate<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> entry) {
return MinecraftReflection.isMinecraftClass(entry.getKey());
}
});
}
initializeSets();
}
@SuppressWarnings("unchecked")
private void initializeSets() throws FieldAccessException {
if (serverPacketsRef == null || clientPacketsRef == null) {
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
try {
if (sets.size() > 1) {
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
// Impossible
if (serverPacketsRef == null || clientPacketsRef == null)
throw new FieldAccessException("Packet sets are in an illegal state.");
// NEVER allow callers to modify the underlying sets
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
// Check sizes
if (serverPackets.size() < MIN_SERVER_PACKETS)
throw new InsufficientPacketsException("Insufficient server packets.", false, serverPackets.size());
if (clientPackets.size() < MIN_CLIENT_PACKETS)
throw new InsufficientPacketsException("Insufficient client packets.", true, clientPackets.size());
} else {
throw new FieldAccessException("Cannot retrieve packet client/server sets.");
}
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access field.", e);
}
} else {
// Copy over again if it has changed
if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size())
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
}
}
/**
* Retrieve the packet mapping.
* @return The packet map.
*/
public Map<Class, Integer> getPacketToID() {
// Initialize it, if we haven't already
if (packetToID == null) {
initialize();
}
return packetToID;
}
private Map<Class, Integer> getSpigotWrapper() throws IllegalAccessException {
// If it talks like a duck, etc.
// Perhaps it would be nice to have a proper duck typing library as well
FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
method(FuzzyMethodContract.newBuilder().
nameExact("size").returnTypeExact(int.class)).
method(FuzzyMethodContract.newBuilder().
nameExact("put").parameterCount(2)).
method(FuzzyMethodContract.newBuilder().
nameExact("get").parameterCount(1)).
build();
Field packetsField = getPacketRegistry().getField(
FuzzyFieldContract.newBuilder().typeMatches(mapLike).build());
Object troveMap = FieldUtils.readStaticField(packetsField, true);
// Fix incorrect no entry values
TroveWrapper.transformNoEntryValue(troveMap, new Function<Integer, Integer>() {
public Integer apply(Integer value) {
if (value >= 0 && value < 256) {
// Someone forgot to set the no entry value. Let's help them.
return -1;
}
return value;
}
});
// We'll assume this a Trove map
return TroveWrapper.getDecoratedMap(troveMap);
}
/**
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
* @return Reflected packet registry.
*/
private FuzzyReflection getPacketRegistry() {
if (packetRegistry == null)
packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true);
return packetRegistry;
}
/**
* Retrieve the injected proxy classes handlig each packet ID.
* @return Injected classes.
*/
public Map<Integer, Class> getOverwrittenPackets() {
return overwrittenPackets;
}
/**
* Retrieve the vanilla classes handling each packet ID.
* @return Vanilla classes.
*/
public Map<Integer, Class> getPreviousPackets() {
return previousValues;
}
/**
* Retrieve every known and supported server packet.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
public Set<Integer> getServerPackets() throws FieldAccessException {
initializeSets();
// Sanity check. This is impossible!
if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS)
throw new FieldAccessException("Server packet list is empty. Seems to be unsupported");
return serverPackets;
}
/**
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public Set<Integer> getClientPackets() throws FieldAccessException {
initializeSets();
// As above
if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS)
throw new FieldAccessException("Client packet list is empty. Seems to be unsupported");
return clientPackets;
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @return The associated class.
*/
public Class getPacketClassFromID(int packetID) {
return getPacketClassFromID(packetID, false);
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
public Class getPacketClassFromID(int packetID, boolean forceVanilla) {
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
Class<?> result = null;
// Optimized lookup
if (lookup.containsKey(packetID)) {
return removeEnhancer(lookup.get(packetID), forceVanilla);
}
// Refresh lookup tables
getPacketToID();
// See if we can look for non-vanilla classes
if (!forceVanilla) {
result = Iterables.getFirst(customIdToPacket.get(packetID), null);
}
if (result == null) {
result = vanillaIdToPacket.get(packetID);
}
// See if we got it
if (result != null)
return result;
else
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
}
/**
* Retrieve the packet ID of a given packet.
* @param packet - the type of packet to check.
* @return The ID of the given packet.
* @throws IllegalArgumentException If this is not a valid packet.
*/
public int getPacketID(Class<?> packet) {
if (packet == null)
throw new IllegalArgumentException("Packet type class cannot be NULL.");
if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
throw new IllegalArgumentException("Type must be a packet.");
// The registry contains both the overridden and original packets
return getPacketToID().get(packet);
}
/**
* Find the first superclass that is not a CBLib proxy object.
* @param clazz - the class whose hierachy we're going to search through.
* @param remove - whether or not to skip enhanced (proxy) classes.
* @return If remove is TRUE, the first superclass that is not a proxy.
*/
private static Class removeEnhancer(Class clazz, boolean remove) {
if (remove) {
// Get the underlying vanilla class
while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) {
clazz = clazz.getSuperclass();
}
}
return clazz;
}
/**
* Occurs when we were unable to retrieve all the packets in the registry.
* @author Kristian
*/
public static class InsufficientPacketsException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final boolean client;
private final int packetCount;
private InsufficientPacketsException(String message, boolean client, int packetCount) {
super(message);
this.client = client;
this.packetCount = packetCount;
}
public boolean isClient() {
return client;
}
public int getPacketCount() {
return packetCount;
}
}
}
@@ -1,96 +0,0 @@
package com.comphenix.protocol.injector.packet;
import javax.annotation.Nonnull;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.base.Preconditions;
/**
* A builder responsible for creating incoming packet injectors.
*
* @author Kristian
*/
public class PacketInjectorBuilder {
protected PacketInjectorBuilder() {
// No need to construct this
}
/**
* Retrieve a new packet injector builder.
* @return Injector builder.
*/
public static PacketInjectorBuilder newBuilder() {
return new PacketInjectorBuilder();
}
protected ListenerInvoker invoker;
protected ErrorReporter reporter;
protected PlayerInjectionHandler playerInjection;
/**
* The error reporter used by the created injector.
* @param reporter - new error reporter.
* @return This builder, for chaining.
*/
public PacketInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
this.reporter = reporter;
return this;
}
/**
* The packet stream invoker.
* @param invoker - the invoker.
* @return This builder, for chaining.
*/
public PacketInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
this.invoker = invoker;
return this;
}
/**
* Set the player injection.
* @param playerInjection - the injection.
* @return This builder, for chaining.
*/
@Nonnull
public PacketInjectorBuilder playerInjection(@Nonnull PlayerInjectionHandler playerInjection) {
Preconditions.checkNotNull(playerInjection, "playerInjection cannot be NULL");
this.playerInjection = playerInjection;
return this;
}
/**
* Called before an object is created with this builder.
*/
private void initializeDefaults() {
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
// Initialize with default values if we can
if (reporter == null)
reporter = ProtocolLibrary.getErrorReporter();
if (invoker == null)
invoker = (PacketFilterManager) manager;
if (playerInjection == null)
throw new IllegalStateException("Player injection parameter must be initialized.");
}
/**
* Create a packet injector using the provided fields or the default values.
* <p>
* Note that any non-null builder parameters must be set.
* @return The created injector.
* @throws FieldAccessException If anything goes wrong in terms of reflection.
*/
public PacketInjector buildInjector() throws FieldAccessException {
initializeDefaults();
return new ProxyPacketInjector(invoker, playerInjection, reporter);
}
}
@@ -1,433 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.packet;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.compat.netty.LegacyProtocolRegistry;
import com.comphenix.protocol.compat.netty.independent.NettyProtocolRegistry;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
import com.comphenix.protocol.injector.packet.LegacyPacketRegistry.InsufficientPacketsException;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.TroveWrapper.CannotFindTroveNoEntryValue;
import com.google.common.base.Function;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* Static packet registry in Minecraft.
* @author Kristian
*/
@SuppressWarnings("rawtypes")
public class PacketRegistry {
public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value.");
public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s");
public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s");
// Two different packet registry
private static volatile LegacyPacketRegistry LEGACY;
private static volatile ProtocolRegistry NETTY;
// Cached for legacy
private static volatile Set<PacketType> NETTY_SERVER_PACKETS;
private static volatile Set<PacketType> NETTY_CLIENT_PACKETS;
// Cached for Netty
private static volatile Set<Integer> LEGACY_SERVER_PACKETS;
private static volatile Set<Integer> LEGACY_CLIENT_PACKETS;
private static volatile Map<Integer, Class> LEGACY_PREVIOUS_PACKETS;
// Whether or not the registry has
private static boolean INITIALIZED;
/**
* Initialize the packet registry.
*/
private static void initialize() {
if (INITIALIZED) {
// Make sure they were initialized
if (NETTY == null && LEGACY == null)
throw new IllegalStateException("No initialized registry.");
return;
}
// Check for netty
if (MinecraftReflection.isUsingNetty()) {
if (NETTY == null) {
if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)) {
NETTY = new NettyProtocolRegistry();
} else {
NETTY = new LegacyProtocolRegistry();
}
}
} else {
initializeLegacy();
}
}
/**
* Determine if the given packet type is supported on the current server.
* @param type - the type to check.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isSupported(PacketType type) {
initialize();
if (NETTY != null)
return NETTY.getPacketTypeLookup().containsKey(type);
// Look up the correct type
return type.isClient() ?
LEGACY.getClientPackets().contains(type.getLegacyId()) :
LEGACY.getServerPackets().contains(type.getLegacyId());
}
/**
* Initialize the legacy packet registry.
*/
private static void initializeLegacy() {
if (LEGACY == null) {
try {
LEGACY = new LegacyPacketRegistry();
LEGACY.initialize();
} catch (InsufficientPacketsException e) {
if (e.isClient()) {
ProtocolLibrary.getErrorReporter().reportWarning(
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(e.getPacketCount())
);
} else {
ProtocolLibrary.getErrorReporter().reportWarning(
PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(e.getPacketCount())
);
}
} catch (CannotFindTroveNoEntryValue e) {
ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class,
Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e.getCause()));
}
}
}
/**
* Retrieve a map of every packet class to every ID.
* <p>
* Deprecated: Use {@link #getPacketToType()} instead.
* @return A map of packet classes and their corresponding ID.
*/
@Deprecated
public static Map<Class, Integer> getPacketToID() {
initialize();
if (NETTY != null) {
@SuppressWarnings("unchecked")
Map<Class, Integer> result = (Map)Maps.transformValues(NETTY.getPacketClassLookup(), new Function<PacketType, Integer>() {
@Override
public Integer apply(PacketType type) {
return type.getLegacyId();
};
});
return result;
}
return LEGACY.getPacketToID();
}
/**
* Retrieve a map of every packet class to the respective packet type.
* @return A map of packet classes and their corresponding packet type.
*/
public static Map<Class, PacketType> getPacketToType() {
initialize();
if (NETTY != null) {
@SuppressWarnings("unchecked")
Map<Class, PacketType> result = (Map)NETTY.getPacketClassLookup();
return result;
}
return Maps.transformValues(LEGACY.getPacketToID(), new Function<Integer, PacketType>() {
@Override
public PacketType apply(Integer packetId) {
return PacketType.findLegacy(packetId);
};
});
}
/**
* Retrieve the injected proxy classes handlig each packet ID.
* <p>
* This is not supported in 1.7.2 and later.
* @return Injected classes.
*/
@Deprecated
public static Map<Integer, Class> getOverwrittenPackets() {
initialize();
if (LEGACY != null)
return LEGACY.getOverwrittenPackets();
throw new IllegalStateException("Not supported on Netty.");
}
/**
* Retrieve the vanilla classes handling each packet ID.
* @return Vanilla classes.
*/
@Deprecated
public static Map<Integer, Class> getPreviousPackets() {
initialize();
if (NETTY != null) {
// Construct it first
if (LEGACY_PREVIOUS_PACKETS == null) {
Map<Integer, Class> map = Maps.newHashMap();
for (Entry<PacketType, Class<?>> entry : NETTY.getPacketTypeLookup().entrySet()) {
map.put(entry.getKey().getLegacyId(), entry.getValue());
}
LEGACY_PREVIOUS_PACKETS = Collections.unmodifiableMap(map);
}
return LEGACY_PREVIOUS_PACKETS;
}
return LEGACY.getPreviousPackets();
}
/**
* Retrieve every known and supported server packet.
* <p>
* Deprecated: Use {@link #getServerPacketTypes()} instead.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
@Deprecated
public static Set<Integer> getServerPackets() throws FieldAccessException {
initialize();
if (NETTY != null) {
if (LEGACY_SERVER_PACKETS == null) {
LEGACY_SERVER_PACKETS = toLegacy(NETTY.getServerPackets());
}
return LEGACY_SERVER_PACKETS;
}
return LEGACY.getServerPackets();
}
/**
* Retrieve every known and supported server packet type.
* @return Every server packet type.
*/
public static Set<PacketType> getServerPacketTypes() {
initialize();
if (NETTY != null) {
NETTY.synchronize();
return NETTY.getServerPackets();
}
// Handle legacy
if (NETTY_SERVER_PACKETS == null) {
NETTY_SERVER_PACKETS = toPacketTypes(LEGACY.getServerPackets(), Sender.SERVER);
}
return NETTY_SERVER_PACKETS;
}
/**
* Retrieve every known and supported client packet.
* <p>
* Deprecated: Use {@link #getClientPacketTypes()} instead.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
@Deprecated
public static Set<Integer> getClientPackets() throws FieldAccessException {
initialize();
if (NETTY != null) {
if (LEGACY_CLIENT_PACKETS == null) {
LEGACY_CLIENT_PACKETS = toLegacy(NETTY.getClientPackets());
}
return LEGACY_CLIENT_PACKETS;
}
return LEGACY.getClientPackets();
}
/**
* Retrieve every known and supported server packet type.
* @return Every server packet type.
*/
public static Set<PacketType> getClientPacketTypes() {
initialize();
if (NETTY != null) {
NETTY.synchronize();
return NETTY.getClientPackets();
}
// Handle legacy
if (NETTY_CLIENT_PACKETS == null) {
NETTY_CLIENT_PACKETS = toPacketTypes(LEGACY.getClientPackets(), Sender.CLIENT);
}
return NETTY_CLIENT_PACKETS;
}
/**
* Convert a set of packet types to a set of integers based on the legacy packet ID.
* @param types - packet type.
* @return Set of integers.
*/
public static Set<Integer> toLegacy(Set<PacketType> types) {
Set<Integer> result = Sets.newHashSet();
for (PacketType type : types)
result.add(type.getLegacyId());
return Collections.unmodifiableSet(result);
}
/**
* Convert a set of legacy packet IDs to packet types.
* @param ids - legacy packet IDs.
* @return Set of packet types.
*/
public static Set<PacketType> toPacketTypes(Set<Integer> ids) {
return toPacketTypes(ids, null);
}
/**
* Convert a set of legacy packet IDs to packet types.
* @param ids - legacy packet IDs.
* @param preference - the sender preference, if any.
* @return Set of packet types.
*/
public static Set<PacketType> toPacketTypes(Set<Integer> ids, Sender preference) {
Set<PacketType> result = Sets.newHashSet();
for (int id : ids)
result.add(PacketType.fromLegacy(id, preference));
return Collections.unmodifiableSet(result);
}
/**
* Retrieves the correct packet class from a given packet ID.
* <p>
* Deprecated: Use {@link #getPacketClassFromType(PacketType)} instead.
* @param packetID - the packet ID.
* @return The associated class.
*/
@Deprecated
public static Class getPacketClassFromID(int packetID) {
initialize();
if (NETTY != null)
return NETTY.getPacketTypeLookup().get(PacketType.findLegacy(packetID));
return LEGACY.getPacketClassFromID(packetID);
}
/**
* Retrieves the correct packet class from a given type.
* @param type - the packet type.
* @return The associated class.
*/
public static Class getPacketClassFromType(PacketType type) {
return getPacketClassFromType(type, false);
}
/**
* Retrieves the correct packet class from a given type.
* <p>
* Note that forceVanillla will be ignored on MC 1.7.2 and later.
* @param type - the packet type.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
public static Class getPacketClassFromType(PacketType type, boolean forceVanilla) {
initialize();
if (NETTY != null)
return NETTY.getPacketTypeLookup().get(type);
return LEGACY.getPacketClassFromID(type.getLegacyId(), forceVanilla);
}
/**
* Retrieves the correct packet class from a given packet ID.
* <p>
* This method has been deprecated.
* @param packetID - the packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
@Deprecated
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
initialize();
if (LEGACY != null)
return LEGACY.getPacketClassFromID(packetID, forceVanilla);
return getPacketClassFromID(packetID);
}
/**
* Retrieve the packet ID of a given packet.
* <p>
* Deprecated: Use {@link #getPacketType(Class)}.
* @param packet - the type of packet to check.
* @return The legacy ID of the given packet.
* @throws IllegalArgumentException If this is not a valid packet.
*/
@Deprecated
public static int getPacketID(Class<?> packet) {
initialize();
if (NETTY != null)
return NETTY.getPacketClassLookup().get(packet).getLegacyId();
return LEGACY.getPacketID(packet);
}
/**
* Retrieve the packet type of a given packet.
* @param packet - the class of the packet.
* @return The packet type, or NULL if not found.
*/
public static PacketType getPacketType(Class<?> packet) {
return getPacketType(packet, null);
}
/**
* Retrieve the packet type of a given packet.
* @param packet - the class of the packet.
* @param sender - the sender of the packet, or NULL.
* @return The packet type, or NULL if not found.
*/
public static PacketType getPacketType(Class<?> packet, Sender sender) {
initialize();
if (NETTY != null) {
return NETTY.getPacketClassLookup().get(packet);
} else {
final int id = LEGACY.getPacketID(packet);
return PacketType.hasLegacy(id) ?
PacketType.fromLegacy(id, sender) : null;
}
}
}
@@ -1,372 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.packet;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
/**
* This class is responsible for adding or removing proxy objects that intercepts received packets.
*
* @author Kristian
*/
class ProxyPacketInjector implements PacketInjector {
public static final ReportType REPORT_CANNOT_FIND_READ_PACKET_METHOD = new ReportType("Cannot find read packet method for ID %s.");
public static final ReportType REPORT_UNKNOWN_ORIGIN_FOR_PACKET = new ReportType("Timeout: Unknown origin %s for packet %s. Are you using GamePhase.LOGIN?");
/**
* Represents a way to update the packet ID to class lookup table.
* @author Kristian
*/
private static interface PacketClassLookup {
public void setLookup(int packetID, Class<?> clazz);
}
private static class IntHashMapLookup implements PacketClassLookup {
private WrappedIntHashMap intHashMap;
public IntHashMapLookup() throws IllegalAccessException {
initialize();
}
@Override
public void setLookup(int packetID, Class<?> clazz) {
intHashMap.put(packetID, clazz);
}
private void initialize() throws IllegalAccessException {
if (intHashMap == null) {
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).
getFieldByType("packetIdMap", MinecraftReflection.getIntHashMapClass());
try {
intHashMap = WrappedIntHashMap.fromHandle(
FieldUtils.readField(intHashMapField, (Object) null, true));
} catch (IllegalArgumentException e) {
throw new RuntimeException("Minecraft is incompatible.", e);
}
}
}
}
private static class ArrayLookup implements PacketClassLookup {
private Class<?>[] array;
public ArrayLookup() throws IllegalAccessException {
initialize();
}
@Override
public void setLookup(int packetID, Class<?> clazz) {
array[packetID] = clazz;
}
private void initialize() throws IllegalAccessException {
FuzzyReflection reflection = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass());
// Is there a Class array with 256 elements instead?
for (Field field : reflection.getFieldListByType(Class[].class)) {
Class<?>[] test = (Class<?>[]) FieldUtils.readField(field, (Object)null);
if (test.length == 256) {
array = test;
return;
}
}
throw new IllegalArgumentException(
"Unable to find an array with the type " + Class[].class +
" in " + MinecraftReflection.getPacketClass());
}
}
/**
* Matches the readPacketData(DataInputStream) method in Packet.
*/
private static FuzzyMethodContract READ_PACKET = FuzzyMethodContract.newBuilder().
returnTypeVoid().
parameterDerivedOf(DataInput.class).
parameterCount(1).
build();
private static PacketClassLookup lookup;
// The packet filter manager
private ListenerInvoker manager;
// Error reporter
private ErrorReporter reporter;
// Allows us to determine the sender
private PlayerInjectionHandler playerInjection;
// Share callback filter
private CallbackFilter filter;
// Determine if the read packet method was found
private boolean readPacketIntercepted = false;
public ProxyPacketInjector(ListenerInvoker manager, PlayerInjectionHandler playerInjection,
ErrorReporter reporter) throws FieldAccessException {
this.manager = manager;
this.playerInjection = playerInjection;
this.reporter = reporter;
initialize();
}
@Override
public boolean isCancelled(Object packet) {
return ReadPacketModifier.isCancelled(packet);
}
@Override
public void setCancelled(Object packet, boolean cancelled) {
if (cancelled) {
ReadPacketModifier.setOverride(packet, null);
} else {
ReadPacketModifier.removeOverride(packet);
}
}
private void initialize() throws FieldAccessException {
if (lookup == null) {
try {
lookup = new IntHashMapLookup();
} catch (Exception e1) {
try {
lookup = new ArrayLookup();
} catch (Exception e2) {
// Wow
throw new FieldAccessException(e1.getMessage() + ". Workaround failed too.", e2);
}
}
// Should work fine now
}
}
@Override
public void inputBuffersChanged(Set<PacketType> set) {
// No need to do anything
}
@Override
@SuppressWarnings({"rawtypes", "deprecation"})
public boolean addPacketHandler(PacketType type, Set<ListenerOptions> options) {
final int packetID = type.getLegacyId();
if (hasPacketHandler(type))
return false;
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
// Unfortunately, we can't easily distinguish between these two functions:
// * Object lookup(int par1)
// * Object removeObject(int par1)
// So, we'll use the classMapToInt registry instead.
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Class old = PacketRegistry.getPacketClassFromType(type);
// If this packet is not known
if (old == null) {
throw new IllegalStateException("Packet ID " + type + " is not a valid packet type in this version.");
}
// Check for previous injections
if (Factory.class.isAssignableFrom(old)) {
throw new IllegalStateException("Packet " + type + " has already been injected.");
}
if (filter == null) {
readPacketIntercepted = false;
filter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Skip methods defined in Object
if (method.getDeclaringClass().equals(Object.class)) {
return 0;
} else if (READ_PACKET.isMatch(MethodInfo.fromMethod(method), null)) {
readPacketIntercepted = true;
return 1;
} else {
return 2;
}
}
};
}
// Subclass the specific packet class
ex.setSuperclass(old);
ex.setCallbackFilter(filter);
ex.setCallbackTypes(new Class<?>[] { NoOp.class, ReadPacketModifier.class, ReadPacketModifier.class });
Class proxy = ex.createClass();
// Create the proxy handlers
ReadPacketModifier modifierReadPacket = new ReadPacketModifier(packetID, this, reporter, true);
ReadPacketModifier modifierRest = new ReadPacketModifier(packetID, this, reporter, false);
// Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
// Check that we found the read method
if (!readPacketIntercepted) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_FIND_READ_PACKET_METHOD).messageParam(packetID));
}
// Override values
previous.put(packetID, old);
registry.put(proxy, packetID);
overwritten.put(packetID, proxy);
lookup.setLookup(packetID, proxy);
return true;
}
@Override
@SuppressWarnings({"rawtypes", "deprecation"})
public boolean removePacketHandler(PacketType type) {
final int packetID = type.getLegacyId();
if (!hasPacketHandler(type))
return false;
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Class old = previous.get(packetID);
Class proxy = PacketRegistry.getPacketClassFromType(type);
lookup.setLookup(packetID, old);
previous.remove(packetID);
registry.remove(proxy);
overwritten.remove(packetID);
return true;
}
/**
* Determine if the data a packet read must be buffered.
* @param packetId - the packet to check.
* @return TRUE if it does, FALSE otherwise.
*/
@Deprecated
public boolean requireInputBuffers(int packetId) {
return manager.requireInputBuffer(packetId);
}
@SuppressWarnings("deprecation")
@Override
public boolean hasPacketHandler(PacketType type) {
return PacketRegistry.getPreviousPackets().containsKey(type.getLegacyId());
}
@SuppressWarnings("deprecation")
@Override
public Set<PacketType> getPacketHandlers() {
return PacketRegistry.toPacketTypes(PacketRegistry.getPreviousPackets().keySet(), Sender.CLIENT);
}
// Called from the ReadPacketModified monitor
public PacketEvent packetRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
if (playerInjection.canRecievePackets()) {
return playerInjection.handlePacketRecieved(packet, input, buffered);
}
try {
Player client = playerInjection.getPlayerByConnection((DataInputStream) input);
// Never invoke a event if we don't know where it's from
if (client != null) {
return packetRecieved(packet, client, buffered);
} else {
// The timeout elapsed!
reporter.reportDetailed(this, Report.newBuilder(REPORT_UNKNOWN_ORIGIN_FOR_PACKET).messageParam(input, packet.getType()));
return null;
}
} catch (InterruptedException e) {
// We will ignore this - it occurs when a player disconnects
//reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input);
return null;
}
}
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
NetworkMarker marker = buffered != null ? new LegacyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered, packet.getType()) : null;
PacketEvent event = PacketEvent.fromClient(manager, packet, marker, client);
manager.invokePacketRecieving(event);
return event;
}
@Override
@SuppressWarnings({"rawtypes", "deprecation"})
public synchronized void cleanupAll() {
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
// Remove every packet handler
for (Integer id : previous.keySet().toArray(new Integer[0])) {
removePacketHandler(PacketType.findLegacy(id, Sender.CLIENT));
removePacketHandler(PacketType.findLegacy(id, Sender.SERVER));
}
overwritten.clear();
previous.clear();
}
}
@@ -1,186 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.packet;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Map;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.NetworkProcessor;
import com.google.common.collect.MapMaker;
class ReadPacketModifier implements MethodInterceptor {
public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet.");
// A cancel marker
private static final Object CANCEL_MARKER = new Object();
// Common for all packets of the same type
private ProxyPacketInjector packetInjector;
private int packetID;
// Report errors
private ErrorReporter reporter;
private NetworkProcessor processor;
// If this is a read packet data method
private boolean isReadPacketDataMethod;
// Whether or not a packet has been cancelled
private static Map<Object, Object> override = new MapMaker().weakKeys().makeMap();
public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
this.packetID = packetID;
this.packetInjector = packetInjector;
this.reporter = reporter;
this.processor = new NetworkProcessor(reporter);
this.isReadPacketDataMethod = isReadPacketDataMethod;
}
/**
* Remove any packet overrides.
* @param packet - the packet to rever
*/
public static void removeOverride(Object packet) {
override.remove(packet);
}
/**
* Retrieve the packet that overrides the methods of the given packet.
* @param packet - the given packet.
* @return Overriden object.
*/
public static Object getOverride(Object packet) {
return override.get(packet);
}
/**
* Set the packet instance to delegate to instead, or mark the packet as cancelled.
* <p>
* To undo a override, use {@link #removeOverride(Object)}.
* @param packet - the packet.
* @param override - the override method. NULL to cancel this packet.
*/
public static void setOverride(Object packet, Object overridePacket) {
override.put(packet, overridePacket != null ? overridePacket : CANCEL_MARKER);
}
/**
* Determine if the given packet has been cancelled before.
* @param packet - the packet to check.
* @return TRUE if it has been cancelled, FALSE otherwise.
*/
public static boolean isCancelled(Object packet) {
return getOverride(packet) == CANCEL_MARKER;
}
@SuppressWarnings("deprecation")
@Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// Atomic retrieval
Object overridenObject = override.get(thisObj);
Object returnValue = null;
// We need this in order to get the correct player
InputStream input = isReadPacketDataMethod ? (InputStream) args[0] : null;
ByteArrayOutputStream bufferStream = null;
// See if we need to buffer the read data
if (isReadPacketDataMethod && packetInjector.requireInputBuffers(packetID)) {
CaptureInputStream captured = new CaptureInputStream(
input, bufferStream = new ByteArrayOutputStream());
// Swap it with our custom stream
args[0] = new DataInputStream(captured);
}
if (overridenObject != null) {
// This packet has been cancelled
if (overridenObject == CANCEL_MARKER) {
// So, cancel all void methods
if (method.getReturnType().equals(Void.TYPE))
return null;
else // Revert to normal for everything else
overridenObject = thisObj;
}
returnValue = proxy.invokeSuper(overridenObject, args);
} else {
returnValue = proxy.invokeSuper(thisObj, args);
}
// Is this a readPacketData method?
if (isReadPacketDataMethod) {
// Swap back custom stream
args[0] = input;
try {
byte[] buffer = bufferStream != null ? bufferStream.toByteArray() : null;
// Let the people know
PacketType type = PacketType.findLegacy(packetID, Sender.CLIENT);
PacketContainer container = new PacketContainer(type, thisObj);
PacketEvent event = packetInjector.packetRecieved(container, input, buffer);
// Handle override
if (event != null) {
Object result = event.getPacket().getHandle();
if (event.isCancelled()) {
override.put(thisObj, CANCEL_MARKER);
return returnValue;
} else if (!objectEquals(thisObj, result)) {
override.put(thisObj, result);
}
// This is fine - received packets are enqueued in any case
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
processor.invokePostEvent(event, marker);
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft cannot handle this error
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e)
);
}
}
return returnValue;
}
private boolean objectEquals(Object a, Object b) {
return System.identityHashCode(a) != System.identityHashCode(b);
}
}
@@ -1,130 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.packet;
import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.lang.reflect.Method;
import java.util.Map;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.NetworkProcessor;
import com.google.common.collect.MapMaker;
public class WritePacketModifier implements MethodInterceptor {
public static final ReportType REPORT_CANNOT_WRITE_SERVER_PACKET = new ReportType("Cannot write server packet.");
private static class ProxyInformation {
// Marker that contains custom writers
public final Object proxyObject;
public final PacketEvent event;
public final NetworkMarker marker;
public ProxyInformation(Object proxyObject, PacketEvent event, NetworkMarker marker) {
this.proxyObject = proxyObject;
this.event = event;
this.marker = marker;
}
}
private Map<Object, ProxyInformation> proxyLookup = new MapMaker().weakKeys().makeMap();
// Report errors
private final ErrorReporter reporter;
private final NetworkProcessor processor;
// Whether or not this represents the write method
private boolean isWriteMethod;
public WritePacketModifier(ErrorReporter reporter, boolean isWriteMethod) {
this.reporter = reporter;
this.processor = new NetworkProcessor(reporter);
this.isWriteMethod = isWriteMethod;
}
/**
* Associate the given generated instance of a class and the given parameteters.
* @param generatedClass - the generated class.
* @param proxyObject - the object to call from the generated class.
* @param event - the packet event.
* @param marker - the network marker.
*/
public void register(Object generatedClass, Object proxyObject, PacketEvent event, NetworkMarker marker) {
proxyLookup.put(generatedClass, new ProxyInformation(proxyObject, event, marker));
}
@Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
ProxyInformation information = proxyLookup.get(thisObj);
if (information == null) {
// This is really bad - someone forgot to register the proxy
throw new RuntimeException("Cannot find proxy information for " + thisObj);
}
if (isWriteMethod) {
// If every output handler has been removed - ignore everything
if (!information.marker.getOutputHandlers().isEmpty()) {
try {
DataOutput output = (DataOutput) args[0];
// First - we need the initial buffer
ByteArrayOutputStream outputBufferStream = new ByteArrayOutputStream();
proxy.invoke(information.proxyObject, new Object[] { new DataOutputStream(outputBufferStream) });
// Let each handler prepare the actual output
byte[] outputBuffer = processor.processOutput(information.event, information.marker,
outputBufferStream.toByteArray());
// Write that output to the network stream
output.write(outputBuffer);
// We're done
processor.invokePostEvent(information.event, information.marker);
return null;
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft cannot handle this error
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_WRITE_SERVER_PACKET).callerParam(args[0]).error(e)
);
}
}
// Invoke this write method first
proxy.invoke(information.proxyObject, args);
processor.invokePostEvent(information.event, information.marker);
return null;
}
// Default to the super method
return proxy.invoke(information.proxyObject, args);
}
}
@@ -1,193 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
/**
* The array list that notifies when packets are sent by the server.
*
* @author Kristian
*/
class InjectedArrayList extends ArrayList<Object> {
public static final ReportType REPORT_CANNOT_REVERT_CANCELLED_PACKET = new ReportType("Reverting cancelled packet failed.");
/**
* Silly Eclipse.
*/
private static final long serialVersionUID = -1173865905404280990L;
// Fake inverted proxy objects
private static ConcurrentMap<Object, Object> delegateLookup = new MapMaker().weakKeys().makeMap();
private transient PlayerInjector injector;
private transient Set<Object> ignoredPackets;
private transient InvertedIntegerCallback callback;
public InjectedArrayList(PlayerInjector injector, Set<Object> ignoredPackets) {
this.injector = injector;
this.ignoredPackets = ignoredPackets;
this.callback = new InvertedIntegerCallback();
}
@Override
public boolean add(Object packet) {
Object result = null;
// Check for fake packets and ignored packets
if (packet instanceof FakePacket) {
return true;
} else if (ignoredPackets.contains(packet)) {
// Don't send it to the filters
result = ignoredPackets.remove(packet);
} else {
result = injector.handlePacketSending(packet);
}
// A NULL packet indicate cancelling
try {
if (result != null) {
super.add(result);
} else {
// We'll use the FakePacket marker instead of preventing the filters
injector.sendServerPacket(createNegativePacket(packet), null, true);
}
// Collection.add contract
return true;
} catch (InvocationTargetException e) {
// Prefer to report this to the user, instead of risking sending it to Minecraft
ProtocolLibrary.getErrorReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_REVERT_CANCELLED_PACKET).error(e).callerParam(packet)
);
// Failure
return false;
}
}
/**
* Used by a hack that reverses the effect of a cancelled packet. Returns a packet
* whereby every int method's return value is inverted (a => -a).
*
* @param source - packet to invert.
* @return The inverted packet.
*/
@SuppressWarnings("deprecation")
Object createNegativePacket(Object source) {
ListenerInvoker invoker = injector.getInvoker();
PacketType type = invoker.getPacketType(source);
// We want to subtract the byte amount that were added to the running
// total of outstanding packets. Otherwise, cancelling too many packets
// might cause a "disconnect.overflow" error.
//
// We do that by constructing a special packet of the same type that returns
// a negative integer for all zero-parameter integer methods. This includes the
// size() method, which is used by the queue method to count the number of
// bytes to add.
//
// Essentially, we have:
//
// public class NegativePacket extends [a packet] {
// @Override
// public int size() {
// return -super.size();
// }
// ect.
// }
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
ex.setSuperclass(MinecraftReflection.getPacketClass());
ex.setInterfaces(new Class[] { FakePacket.class } );
ex.setUseCache(true);
ex.setCallbackType(InvertedIntegerCallback.class);
Class<?> proxyClass = ex.createClass();
Enhancer.registerCallbacks(proxyClass, new Callback[] { callback });
try {
// Temporarily associate the fake packet class
invoker.registerPacketClass(proxyClass, type.getLegacyId());
Object proxy = proxyClass.newInstance();
InjectedArrayList.registerDelegate(proxy, source);
return proxy;
} catch (Exception e) {
// Don't pollute the throws tree
throw new RuntimeException("Cannot create fake class.", e);
} finally {
// Remove this association
invoker.unregisterPacketClass(proxyClass);
}
}
/**
* Ensure that the inverted integer proxy uses the given object as source.
* @param proxy - inverted integer proxy.
* @param source - source object.
*/
private static void registerDelegate(Object proxy, Object source) {
delegateLookup.put(proxy, source);
}
/**
* Inverts the integer result of every integer method.
* @author Kristian
*/
private class InvertedIntegerCallback implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
final Object delegate = delegateLookup.get(obj);
if (delegate == null) {
throw new IllegalStateException("Unable to find delegate source for " + obj);
}
if (method.getReturnType().equals(int.class) && args.length == 0) {
Integer result = (Integer) proxy.invoke(delegate, args);
return -result;
} else {
return proxy.invoke(delegate, args);
}
}
}
}
@@ -1,442 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Used to ensure that the 1.3 server is referencing the correct server handler.
*
* @author Kristian
*/
public class InjectedServerConnection {
// A number of things can go wrong ...
public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
private static Field listenerThreadField;
private static Field minecraftServerField;
private static Field listField;
private static Field dedicatedThreadField;
private static Method serverConnectionMethod;
private List<VolatileField> listFields;
private List<ReplacedArrayList<Object>> replacedLists;
// The current detected server socket
public enum ServerSocketType {
SERVER_CONNECTION,
LISTENER_THREAD,
}
// Used to inject net handlers
private NetLoginInjector netLoginInjector;
// Inject server connections
private AbstractInputStreamLookup socketInjector;
// Detected by the initializer
private ServerSocketType socketType;
private Server server;
private ErrorReporter reporter;
private boolean hasAttempted;
private boolean hasSuccess;
private Object minecraftServer = null;
public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
this.reporter = reporter;
this.server = server;
this.socketInjector = socketInjector;
this.netLoginInjector = netLoginInjector;
}
/**
* Retrieve the current server connection.
* @param reporter - error reproter.
* @param server - the current server.
* @return The current server connection, or NULL if it hasn't been initialized yet.
* @throws FieldAccessException Reflection error.
*/
public static Object getServerConnection(ErrorReporter reporter, Server server) {
try {
// Now we are probably able to check for Netty
InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null);
return inspector.getServerConnection();
} catch (IllegalAccessException e) {
throw new FieldAccessException("Reflection error.", e);
} catch (IllegalArgumentException e) {
throw new FieldAccessException("Corrupt data.", e);
} catch (InvocationTargetException e) {
throw new FieldAccessException("Minecraft error.", e);
}
}
/**
* Initial reflective detective work. Will be automatically called by most methods in this class.
*/
public void initialize() {
// Only execute this method once
if (!hasAttempted)
hasAttempted = true;
else
return;
if (minecraftServerField == null)
minecraftServerField = FuzzyReflection.fromObject(server, true).
getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass());
try {
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
return;
}
try {
if (serverConnectionMethod == null)
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
getMethodByParameters("getServerConnection",
MinecraftReflection.getServerConnectionClass(), new Class[] {});
// We're using Minecraft 1.3.1
socketType = ServerSocketType.SERVER_CONNECTION;
} catch (IllegalArgumentException e) {
// Minecraft 1.2.5 or lower
socketType = ServerSocketType.LISTENER_THREAD;
} catch (Exception e) {
// Oh damn - inform the player
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
}
}
/**
* Retrieve the known server socket type.
* <p>
* This depends on the version of CraftBukkit we are using.
* @return The server socket type.
*/
public ServerSocketType getServerSocketType() {
return socketType;
}
/**
* Inject the connection interceptor into the correct server socket implementation.
*/
public void injectList() {
initialize();
if (socketType == ServerSocketType.SERVER_CONNECTION) {
injectServerConnection();
} else if (socketType == ServerSocketType.LISTENER_THREAD) {
injectListenerThread();
} else {
// Damn it
throw new IllegalStateException("Unable to detected server connection.");
}
}
/**
* Retrieve the listener thread field.
*/
private void initializeListenerField() {
if (listenerThreadField == null)
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
}
/**
* Retrieve the listener thread object, or NULL the server isn't using this socket implementation.
* @return The listener thread, or NULL.
* @throws IllegalAccessException Cannot access field.
* @throws RuntimeException Unexpected class structure - the field doesn't exist.
*/
public Object getListenerThread() throws RuntimeException, IllegalAccessException {
initialize();
if (socketType == ServerSocketType.LISTENER_THREAD) {
initializeListenerField();
return listenerThreadField.get(minecraftServer);
} else {
return null;
}
}
/**
* Retrieve the server connection object, or NULL if the server isn't using it as the socket implementation.
* @return The socket connection, or NULL.
* @throws IllegalAccessException If the reflective operation failed.
* @throws IllegalArgumentException If the reflective operation failed.
* @throws InvocationTargetException If the reflective operation failed.
*/
public Object getServerConnection() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
initialize();
if (socketType == ServerSocketType.SERVER_CONNECTION)
return serverConnectionMethod.invoke(minecraftServer);
else
return null;
}
private void injectListenerThread() {
try {
initializeListenerField();
} catch (RuntimeException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
);
return;
}
Object listenerThread = null;
// Attempt to get the thread
try {
listenerThread = getListenerThread();
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
return;
}
// Inject the server socket too
injectServerSocket(listenerThread);
// Just inject every list field we can get
injectEveryListField(listenerThread, 1);
hasSuccess = true;
}
private void injectServerConnection() {
Object serverConnection = null;
// Careful - we might fail
try {
serverConnection = getServerConnection();
} catch (Exception e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
);
return;
}
if (listField == null)
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
getFieldByType("netServerHandlerList", List.class);
if (dedicatedThreadField == null) {
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).
getFieldListByType(Thread.class);
// Verify the field count
if (matches.size() != 1)
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
);
else
dedicatedThreadField = matches.get(0);
}
// Next, try to get the dedicated thread
try {
if (dedicatedThreadField != null) {
Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
// Inject server socket and NetServerHandlers.
injectServerSocket(dedicatedThread);
injectEveryListField(dedicatedThread, 1);
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
}
injectIntoList(serverConnection, listField);
hasSuccess = true;
}
private void injectServerSocket(Object container) {
socketInjector.inject(container);
}
/**
* Automatically inject into every List-compatible public or private field of the given object.
* @param container - container object with the fields to inject.
* @param minimum - the minimum number of fields we expect exists.
*/
private void injectEveryListField(Object container, int minimum) {
// Ok, great. Get every list field
List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
for (Field list : lists) {
injectIntoList(container, list);
}
// Warn about unexpected errors
if (lists.size() < minimum) {
reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
}
}
@SuppressWarnings("unchecked")
private void injectIntoList(Object instance, Field field) {
VolatileField listFieldRef = new VolatileField(field, instance, true);
List<Object> list = (List<Object>) listFieldRef.getValue();
// Careful not to inject twice
if (list instanceof ReplacedArrayList) {
replacedLists.add((ReplacedArrayList<Object>) list);
} else {
ReplacedArrayList<Object> injectedList = createReplacement(list);
replacedLists.add(injectedList);
listFieldRef.setValue(injectedList);
listFields.add(listFieldRef);
}
}
// Hack to avoid the "moved to quickly" error
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
return new ReplacedArrayList<Object>(list) {
/**
* Shut up Eclipse!
*/
private static final long serialVersionUID = 2070481080950500367L;
// Object writer we'll use
private final ObjectWriter writer = new ObjectWriter();
@Override
protected void onReplacing(Object inserting, Object replacement) {
// Is this a normal Minecraft object?
if (!(inserting instanceof Factory)) {
// If so, copy the content of the old element to the new
try {
writer.copyTo(inserting, replacement, inserting.getClass());
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(InjectedServerConnection.this,
Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
);
}
}
}
@Override
protected void onInserting(Object inserting) {
// Ready for some login handler injection?
if (MinecraftReflection.isLoginHandler(inserting)) {
Object replaced = netLoginInjector.onNetLoginCreated(inserting);
// Only replace if it has changed
if (inserting != replaced)
addMapping(inserting, replaced, true);
}
}
@Override
protected void onRemoved(Object removing) {
// Clean up?
if (MinecraftReflection.isLoginHandler(removing)) {
netLoginInjector.cleanup(removing);
}
}
};
}
/**
* Replace the server handler instance kept by the "keep alive" object.
* @param oldHandler - old server handler.
* @param newHandler - new, proxied server handler.
*/
public void replaceServerHandler(Object oldHandler, Object newHandler) {
if (!hasAttempted) {
injectList();
}
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.addMapping(oldHandler, newHandler);
}
}
}
/**
* Revert to the old vanilla server handler, if it has been replaced.
* @param oldHandler - old vanilla server handler.
*/
public void revertServerHandler(Object oldHandler) {
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.removeMapping(oldHandler);
}
}
}
/**
* Undoes everything.
*/
public void cleanupAll() {
if (replacedLists.size() > 0) {
// Repair the underlying lists
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.revertAll();
}
for (VolatileField field : listFields) {
field.revertValue();
}
listFields.clear();
replacedLists.clear();
}
}
}
@@ -1,162 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.util.concurrent.ConcurrentMap;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;
/**
* Injects every NetLoginHandler created by the server.
*
* @author Kristian
*/
class NetLoginInjector {
public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
// Handles every hook
private ProxyPlayerInjectionHandler injectionHandler;
// Create temporary players
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
// The current error reporter
private ErrorReporter reporter;
private Server server;
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
this.reporter = reporter;
this.server = server;
this.injectionHandler = injectionHandler;
}
/**
* Invoked when a NetLoginHandler has been created.
* @param inserting - the new NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
*/
public Object onNetLoginCreated(Object inserting) {
try {
// Make sure we actually need to inject during this phase
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
return inserting;
Player temporary = playerFactory.createTemporaryPlayer(server);
// Note that we bail out if there's an existing player injector
PlayerInjector injector = injectionHandler.injectPlayer(
temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
if (injector != null) {
// Update injector as well
TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
injector.updateOnLogin = true;
// Save the login
injectedLogins.putIfAbsent(inserting, injector);
}
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
return inserting;
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft can't handle this, so we'll deal with it here
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
messageParam(MinecraftReflection.getNetLoginHandlerName()).
callerParam(inserting, injectionHandler).
error(e)
);
return inserting;
}
}
/**
* Invoked when a NetLoginHandler should be reverted.
* @param inserting - the original NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
*/
public synchronized void cleanup(Object removing) {
PlayerInjector injected = injectedLogins.get(removing);
if (injected != null) {
try {
PlayerInjector newInjector = null;
Player player = injected.getPlayer();
// Clean up list
injectedLogins.remove(removing);
// No need to clean up twice
if (injected.isClean())
return;
// Hack to clean up other references
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
injectionHandler.uninjectPlayer(player);
// Update NetworkManager
if (newInjector != null) {
if (injected instanceof NetworkObjectInjector) {
newInjector.setNetworkManager(injected.getNetworkManager(), true);
}
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Don't leak this to Minecraft
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
messageParam(MinecraftReflection.getNetLoginHandlerName()).
callerParam(removing).
error(e)
);
}
}
}
/**
* Remove all injected hooks.
*/
public void cleanupAll() {
for (PlayerInjector injector : injectedLogins.values()) {
injector.cleanupAll();
}
injectedLogins.clear();
}
}
@@ -1,221 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.Sets;
/**
* Injection hook that overrides the packet queue lists in NetworkHandler.
*
* @author Kristian
*/
class NetworkFieldInjector extends PlayerInjector {
/**
* Marker interface that indicates a packet is fake and should not be processed.
* @author Kristian
*/
public interface FakePacket {
// Nothing
}
// After commit 336a4e00668fd2518c41242755ed6b3bdc3b0e6c (Update CraftBukkit to Minecraft 1.4.4.),
// CraftBukkit stopped redirecting map chunk and map chunk bulk packets to a separate queue.
// Thus, NetworkFieldInjector can safely handle every packet (though not perfectly - some packets
// will be slightly processed).
private MinecraftVersion safeVersion = new MinecraftVersion("1.4.4");
// Packets to ignore
private Set<Object> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Object, Boolean>());
// Overridden fields
private List<VolatileField> overridenLists = new ArrayList<VolatileField>();
// Sync field
private static Field syncField;
private Object syncObject;
// Determine if we're listening
private IntegerSet sendingFilters;
public NetworkFieldInjector(ErrorReporter reporter, Player player, ListenerInvoker manager, IntegerSet sendingFilters) {
super(reporter, player, manager);
this.sendingFilters = sendingFilters;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public synchronized void initialize(Object injectionSource) throws IllegalAccessException {
super.initialize(injectionSource);
// Get the sync field as well
if (hasInitialized) {
if (syncField == null)
syncField = FuzzyReflection.fromObject(networkManager, true).getFieldByType("java\\.lang\\.Object");
syncObject = FieldUtils.readField(syncField, networkManager, true);
}
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
if (networkManager != null) {
try {
if (!filtered) {
ignoredPackets.add(packet);
}
if (marker != null) {
queuedMarkers.put(packet, marker);
}
// Note that invocation target exception is a wrapper for a checked exception
queueMethod.invoke(networkManager, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access queue method.", e);
}
} else {
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
}
}
@Override
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
if (version != null && version.compareTo(safeVersion) > 0) {
return null;
} else {
@SuppressWarnings("deprecation")
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
}
}
@Override
public void injectManager() {
if (networkManager != null) {
@SuppressWarnings("rawtypes")
StructureModifier<List> list = networkModifier.withType(List.class);
// Subclass both send queues
for (Field field : list.getFields()) {
VolatileField overwriter = new VolatileField(field, networkManager, true);
@SuppressWarnings("unchecked")
List<Object> minecraftList = (List<Object>) overwriter.getOldValue();
synchronized(syncObject) {
// The list we'll be inserting
List<Object> hackedList = new InjectedArrayList(this, ignoredPackets);
// Add every previously stored packet
for (Object packet : minecraftList) {
hackedList.add(packet);
}
// Don' keep stale packets around
minecraftList.clear();
overwriter.setValue(Collections.synchronizedList(hackedList));
}
overridenLists.add(overwriter);
}
}
}
@Override
@SuppressWarnings("unchecked")
protected void cleanHook() {
// Clean up
for (VolatileField overriden : overridenLists) {
List<Object> minecraftList = (List<Object>) overriden.getOldValue();
List<Object> hacketList = (List<Object>) overriden.getValue();
if (minecraftList == hacketList) {
return;
}
// Get a lock before we modify the list
synchronized(syncObject) {
try {
// Copy over current packets
for (Object packet : (List<Object>) overriden.getValue()) {
minecraftList.add(packet);
}
} finally {
overriden.revertValue();
}
}
}
overridenLists.clear();
}
@Override
public void handleDisconnect() {
// No need to do anything
}
@Override
public boolean canInject(GamePhase phase) {
// All phases should work
return true;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_HANDLER_FIELDS;
}
}
@@ -1,230 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.LazyLoader;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftVersion;
/**
* Injection method that overrides the NetworkHandler itself, and its queue-method.
*
* @author Kristian
*/
public class NetworkObjectInjector extends PlayerInjector {
// Determine if we're listening
private IntegerSet sendingFilters;
// After commit 336a4e00668fd2518c41242755ed6b3bdc3b0e6c (Update CraftBukkit to Minecraft 1.4.4.),
// CraftBukkit stopped redirecting map chunk and map chunk bulk packets to a separate queue.
// Thus, NetworkFieldInjector can safely handle every packet (though not perfectly - some packets
// will be slightly processed).
private MinecraftVersion safeVersion = new MinecraftVersion("1.4.4");
// Shared callback filter - avoid creating a new class every time
private volatile static CallbackFilter callbackFilter;
// Temporary player factory
private static volatile TemporaryPlayerFactory tempPlayerFactory;
/**
* Create a new network object injector.
* <p>
* Note: This class is intended to be internal. Do not use.
* @param reporter - the error reporter.
* @param player - the player Bukkit entity.
* @param invoker - the packet invoker.
* @param sendingFilters - list of permitted packet IDs.
* @throws IllegalAccessException If reflection failed.
*/
public NetworkObjectInjector(ErrorReporter reporter, Player player,
ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException {
super(reporter, player, invoker);
this.sendingFilters = sendingFilters;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
/**
* Create a temporary player for use during login.
* @param server - Bukkit server.
* @return The temporary player.
*/
public Player createTemporaryPlayer(Server server) {
if (tempPlayerFactory == null)
tempPlayerFactory = new TemporaryPlayerFactory();
// Create and associate the fake player with this network injector
return tempPlayerFactory.createTemporaryPlayer(server, this);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue();
if (networkDelegate != null) {
try {
if (marker != null) {
queuedMarkers.put(packet, marker);
}
// Note that invocation target exception is a wrapper for a checked exception
queueMethod.invoke(networkDelegate, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access queue method.", e);
}
} else {
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
}
}
@Override
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
if (version != null && version.compareTo(safeVersion) > 0) {
return null;
} else {
@SuppressWarnings("deprecation")
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_OBJECT_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
}
}
@Override
public void injectManager() {
if (networkManager != null) {
final Class<?> networkInterface = networkManagerRef.getField().getType();
final Object networkDelegate = networkManagerRef.getOldValue();
if (!networkInterface.isInterface()) {
throw new UnsupportedOperationException(
"Must use CraftBukkit 1.3.0 or later to inject into into NetworkMananger.");
}
Callback queueFilter = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object packet = args[0];
if (packet != null) {
packet = handlePacketSending(packet);
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
}
// Delegate to our underlying class
return proxy.invokeSuper(networkDelegate, args);
}
};
Callback dispatch = new LazyLoader() {
@Override
public Object loadObject() throws Exception {
return networkDelegate;
}
};
// Share callback filter - that way, we avoid generating a new class every time.
if (callbackFilter == null) {
callbackFilter = new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.equals(queueMethod))
return 0;
else
return 1;
}
};
}
// Create our proxy object
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
ex.setSuperclass(networkInterface);
ex.setCallbacks(new Callback[] { queueFilter, dispatch });
ex.setCallbackFilter(callbackFilter);
// Inject it, if we can.
networkManagerRef.setValue(ex.create());
}
}
@Override
protected void cleanHook() {
// Clean up
if (networkManagerRef != null && networkManagerRef.isCurrentSet()) {
networkManagerRef.revertValue();
}
}
@Override
public void handleDisconnect() {
// No need to do anything
}
@Override
public boolean canInject(GamePhase phase) {
// Works for all phases
return true;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_MANAGER_OBJECT;
}
}
@@ -1,367 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
/**
* Represents a player hook into the NetServerHandler class.
*
* @author Kristian
*/
class NetworkServerInjector extends PlayerInjector {
// Disconnected field
public static final ReportType REPORT_ASSUMING_DISCONNECT_FIELD = new ReportType("Unable to find 'disconnected' field. Assuming %s.");
public static final ReportType REPORT_DISCONNECT_FIELD_MISSING = new ReportType("Cannot find disconnected field. Is ProtocolLib up to date?");
public static final ReportType REPORT_DISCONNECT_FIELD_FAILURE = new ReportType("Unable to update disconnected field. Player quit event may be sent twice.");
private volatile static CallbackFilter callbackFilter;
private volatile static boolean foundSendPacket;
private volatile static Field disconnectField;
private InjectedServerConnection serverInjection;
// Determine if we're listening
private IntegerSet sendingFilters;
// Whether or not the player has disconnected
private boolean hasDisconnected;
// Used to copy fields
private final ObjectWriter writer = new ObjectWriter();
public NetworkServerInjector(
ErrorReporter reporter, Player player,
ListenerInvoker invoker, IntegerSet sendingFilters,
InjectedServerConnection serverInjection) {
super(reporter, player, invoker);
this.sendingFilters = sendingFilters;
this.serverInjection = serverInjection;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
if (serverDelegate != null) {
try {
if (marker != null) {
queuedMarkers.put(packet, marker);
}
// Note that invocation target exception is a wrapper for a checked exception
MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access send packet method.", e);
}
} else {
throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
}
}
@Override
public void injectManager() {
if (serverHandlerRef == null)
throw new IllegalStateException("Cannot find server handler.");
// Don't inject twice
if (serverHandlerRef.getValue() instanceof Factory)
return;
if (!tryInjectManager()) {
Class<?> serverHandlerClass = MinecraftReflection.getPlayerConnectionClass();
// Try to override the proxied object
if (proxyServerField != null) {
serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
serverHandler = serverHandlerRef.getValue();
if (serverHandler == null)
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
else
serverHandlerClass = serverHandler.getClass();
// Try again
if (tryInjectManager()) {
// It worked - probably
return;
}
}
throw new RuntimeException(
"Cannot hook player: Unable to find a valid constructor for the "
+ serverHandlerClass.getName() + " object.");
}
}
private boolean tryInjectManager() {
Class<?> serverClass = serverHandler.getClass();
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
Callback sendPacketCallback = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object packet = args[0];
if (packet != null) {
packet = handlePacketSending(packet);
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
}
// Call the method directly
return proxy.invokeSuper(obj, args);
};
};
Callback noOpCallback = NoOp.INSTANCE;
// Share callback filter - that way, we avoid generating a new class for
// every logged in player.
if (callbackFilter == null) {
callbackFilter = new SendMethodFilter();
}
ex.setSuperclass(serverClass);
ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
ex.setCallbackFilter(callbackFilter);
// Find the Minecraft NetServerHandler superclass
Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
DefaultInstances serverInstances = null;
// Maybe the proxy instance can help?
Object proxyInstance = getProxyServerHandler();
// Use the existing server proxy when we create one
if (proxyInstance != null && proxyInstance != serverHandler) {
serverInstances = DefaultInstances.fromArray(generator,
ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
} else {
serverInstances = DefaultInstances.fromArray(generator);
}
serverInstances.setNonNull(true);
serverInstances.setMaximumRecursion(1);
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
// Inject it now
if (proxyObject != null) {
// Did we override a sendPacket method?
if (!foundSendPacket) {
throw new IllegalArgumentException("Unable to find a sendPacket method in " + serverClass);
}
serverInjection.replaceServerHandler(serverHandler, proxyObject);
serverHandlerRef.setValue(proxyObject);
return true;
} else {
return false;
}
}
private Object getProxyServerHandler() {
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
try {
return FieldUtils.readField(proxyServerField, serverHandler, true);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Oh well
}
}
return null;
}
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
if (MinecraftReflection.isMinecraftClass(clazz))
return clazz;
else if (clazz.equals(Object.class))
return clazz;
else
return getFirstMinecraftSuperClass(clazz.getSuperclass());
}
@Override
protected void cleanHook() {
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
serverHandlerRef.revertValue();
try {
if (getNetHandler() != null) {
// Restore packet listener
try {
FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
} catch (IllegalAccessException e) {
// Oh well
e.printStackTrace();
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// Prevent the PlayerQuitEvent from being sent twice
if (hasDisconnected) {
setDisconnect(serverHandlerRef.getValue(), true);
}
}
serverInjection.revertServerHandler(serverHandler);
}
@Override
public void handleDisconnect() {
hasDisconnected = true;
}
/**
* Set the disconnected field in a NetServerHandler.
* @param handler - the NetServerHandler.
* @param value - the new value.
*/
private void setDisconnect(Object handler, boolean value) {
// Set it
try {
// Load the field
if (disconnectField == null) {
disconnectField = FuzzyReflection.fromObject(handler).getFieldByName("disconnected.*");
}
FieldUtils.writeField(disconnectField, handler, value);
} catch (IllegalArgumentException e) {
// Assume it's the first ...
if (disconnectField == null) {
disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class);
reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUMING_DISCONNECT_FIELD).messageParam(disconnectField));
// Try again
if (disconnectField != null) {
setDisconnect(handler, value);
return;
}
}
// This is really bad
reporter.reportDetailed(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_MISSING).error(e));
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_FAILURE).error(e));
}
}
@Override
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
// We support everything
return null;
}
@Override
public boolean canInject(GamePhase phase) {
// Doesn't work when logging in
return phase == GamePhase.PLAYING;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
/**
* Represents a CallbackFilter that only matches the SendPacket method.
* @author Kristian
*/
private static class SendMethodFilter implements CallbackFilter {
private Method sendPacket = MinecraftMethods.getSendPacketMethod();
@Override
public int accept(Method method) {
if (isCallableEqual(sendPacket, method)) {
NetworkServerInjector.foundSendPacket = true;
return 0;
} else {
return 1;
}
}
/**
* Determine if the two methods are equal in terms of call semantics.
* <p>
* Two methods are equal if they have the same name, parameter types and return type.
* @param first - first method.
* @param second - second method.
* @return TRUE if they are, FALSE otherwise.
*/
private boolean isCallableEqual(Method first, Method second) {
return first.getName().equals(second.getName()) &&
first.getReturnType().equals(second.getReturnType()) &&
Arrays.equals(first.getParameterTypes(), second.getParameterTypes());
}
}
}
@@ -1,204 +0,0 @@
package com.comphenix.protocol.injector.player;
import java.io.DataInputStream;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.util.Set;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.compat.netty.WrappedChannel;
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.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
public interface PlayerInjectionHandler {
/**
* How to handle a previously existing player injection.
*
* @author Kristian
*/
public enum ConflictStrategy {
/**
* Override it.
*/
OVERRIDE,
/**
* Immediately exit.
*/
BAIL_OUT;
}
/**
* Retrieve the protocol version of the given player.
* @param player - the player.
* @return The protocol version, or {@link Integer#MIN_VALUE}.
*/
public abstract int getProtocolVersion(Player player);
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
public abstract PlayerInjectHooks getPlayerHook();
/**
* Retrieves how the server packets are read.
* @param phase - the current game phase.
* @return Injection method for reading server packets.
*/
public abstract PlayerInjectHooks getPlayerHook(GamePhase phase);
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
public abstract void setPlayerHook(PlayerInjectHooks playerHook);
/**
* Sets how the server packets are read.
* @param phase - the current game phase.
* @param playerHook - the new injection method for reading server packets.
*/
public abstract void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook);
/**
* Add an underlying packet handler of the given type.
* @param type - packet type to register.
* @param options - any specified listener options.
*/
public abstract void addPacketHandler(PacketType type, Set<ListenerOptions> options);
/**
* Remove an underlying packet handler of this type.
* @param type - packet type to unregister.
*/
public abstract void removePacketHandler(PacketType type);
/**
* Retrieve a player by its DataInput connection.
* @param inputStream - the associated DataInput connection.
* @return The player.
* @throws InterruptedException If the thread was interrupted during the wait.
*/
public abstract Player getPlayerByConnection(DataInputStream inputStream)
throws InterruptedException;
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This call will be ignored if there's no listener that can receive the given events.
* @param player - player to hook.
* @param strategy - how to handle injection conflicts.
*/
public abstract void injectPlayer(Player player, ConflictStrategy strategy);
/**
* Invoke special routines for handling disconnect before a player is uninjected.
* @param player - player to process.
*/
public abstract void handleDisconnect(Player player);
/**
* Uninject the given player.
* @param player - player to uninject.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
public abstract boolean uninjectPlayer(Player player);
/**
* Unregisters a player by the given address.
* <p>
* If the server handler has been created before we've gotten a chance to unject the player,
* the method will try a workaround to remove the injected hook in the NetServerHandler.
*
* @param address - address of the player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
public abstract boolean uninjectPlayer(InetSocketAddress address);
/**
* Send the given packet to the given receiver.
* @param receiver - the player receiver.
* @param packet - the packet to send.
* @param marker - network marker.
* @param filters - whether or not to invoke the packet filters.
* @throws InvocationTargetException If an error occurred during sending.
*/
public abstract void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters)
throws InvocationTargetException;
/**
* Process a packet as if it were sent by the given player.
* @param player - the sender.
* @param mcPacket - the packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
public abstract void recieveClientPacket(Player player, Object mcPacket)
throws IllegalAccessException, InvocationTargetException;
/**
* Ensure that packet readers are informed of this player reference.
* @param player - the player to update.
*/
public abstract void updatePlayer(Player player);
/**
* Determine if the given listeners are valid.
* @param listeners - listeners to check.
*/
public abstract void checkListener(Set<PacketListener> listeners);
/**
* Determine if a listener is valid or not.
* <p>
* If not, a warning will be printed to the console.
* @param listener - listener to check.
*/
public abstract void checkListener(PacketListener listener);
/**
* Retrieve the current list of registered sending listeners.
* @return List of the sending listeners's packet IDs.
*/
public abstract Set<PacketType> getSendingFilters();
/**
* Whether or not this player injection handler can also receive packets.
* @return TRUE if it can, FALSE otherwise.
*/
public abstract boolean canRecievePackets();
/**
* Invoked if this player injection handler can process received packets.
* @param packet - the received packet.
* @param input - the input stream.
* @param buffered - the buffered packet.
* @return The packet event.
*/
public abstract PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered);
/**
* Close any lingering proxy injections.
*/
public abstract void close();
/**
* Determine if we have packet listeners with the given type that must be executed on the main thread.
* <p>
* This only applies for onPacketSending(), as it makes certain guarantees.
* @param type - the packet type.
* @return TRUE if we do, FALSE otherwise.
*/
public abstract boolean hasMainThreadListener(PacketType type);
public abstract WrappedChannel getChannel(Player player);
}
@@ -1,760 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import net.sf.cglib.proxy.Factory;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.packet.InterceptWritePacket;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.MapMaker;
public abstract class PlayerInjector implements SocketInjector {
// Disconnect method related reports
public static final ReportType REPORT_ASSUME_DISCONNECT_METHOD = new ReportType("Cannot find disconnect method by name. Assuming %s.");
public static final ReportType REPORT_INVALID_ARGUMENT_DISCONNECT = new ReportType("Invalid argument passed to disconnect method: %s");
public static final ReportType REPORT_CANNOT_ACCESS_DISCONNECT = new ReportType("Unable to access disconnect method.");
public static final ReportType REPORT_CANNOT_CLOSE_SOCKET = new ReportType("Unable to close socket.");
public static final ReportType REPORT_ACCESS_DENIED_CLOSE_SOCKET = new ReportType("Insufficient permissions. Cannot close socket.");
public static final ReportType REPORT_DETECTED_CUSTOM_SERVER_HANDLER =
new ReportType("Detected server handler proxy type by another plugin. Conflict may occur!");
public static final ReportType REPORT_CANNOT_PROXY_SERVER_HANDLER = new ReportType("Unable to load server handler from proxy type.");
public static final ReportType REPORT_CANNOT_UPDATE_PLAYER = new ReportType("Cannot update player in PlayerEvent.");
public static final ReportType REPORT_CANNOT_HANDLE_PACKET = new ReportType("Cannot handle server packet.");
public static final ReportType REPORT_INVALID_NETWORK_MANAGER = new ReportType("NetworkManager doesn't appear to be valid.");
// Net login handler stuff
private static Field netLoginNetworkField;
// Different disconnect methods
private static Method loginDisconnect;
private static Method serverDisconnect;
// Cache previously retrieved fields
protected static Field serverHandlerField;
protected static Field proxyServerField;
protected static Field networkManagerField;
protected static Field netHandlerField;
protected static Field socketField;
protected static Field socketAddressField;
private static Field inputField;
private static Field entityPlayerField;
// Whether or not we're using a proxy type
private static boolean hasProxyType;
// To add our injected array lists
protected static StructureModifier<Object> networkModifier;
// And methods
protected static Method queueMethod;
protected static Method processMethod;
protected volatile Player player;
protected boolean hasInitialized;
// Reference to the player's network manager
protected VolatileField networkManagerRef;
protected VolatileField serverHandlerRef;
protected Object networkManager;
// Current net handler
protected Object loginHandler;
protected Object serverHandler;
protected Object netHandler;
// Current socket and address
protected Socket socket;
protected SocketAddress socketAddress;
// The packet manager and filters
protected ListenerInvoker invoker;
// Previous data input
protected DataInputStream cachedInput;
// Handle errors
protected ErrorReporter reporter;
// Previous markers
protected Map<Object, NetworkMarker> queuedMarkers = new MapMaker().weakKeys().makeMap();
protected InterceptWritePacket writePacketInterceptor;
// Whether or not the injector has been cleaned
private boolean clean;
// Whether or not to update the current player on the first Packet1Login
boolean updateOnLogin;
volatile Player updatedPlayer;
public PlayerInjector(ErrorReporter reporter, Player player, ListenerInvoker invoker) {
this.reporter = reporter;
this.player = player;
this.invoker = invoker;
this.writePacketInterceptor = invoker.getInterceptWritePacket();
}
/**
* Retrieve the notch (NMS) entity player object.
* @param player - the player to retrieve.
* @return Notch player object.
*/
protected Object getEntityPlayer(Player player) {
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
return unwrapper.unwrapItem(player);
}
/**
* Initialize all fields for this player injector, if it hasn't already.
* @param injectionSource - Injection source
* @throws IllegalAccessException An error has occured.
*/
public void initialize(Object injectionSource) throws IllegalAccessException {
if (injectionSource == null)
throw new IllegalArgumentException("injectionSource cannot be NULL");
//Dispatch to the correct injection method
if (injectionSource instanceof Player)
initializePlayer((Player) injectionSource);
else if (MinecraftReflection.isLoginHandler(injectionSource))
initializeLogin(injectionSource);
else
throw new IllegalArgumentException("Cannot initialize a player hook using a " + injectionSource.getClass().getName());
}
/**
* Initialize the player injector using an actual player instance.
* @param player - the player to hook.
*/
public void initializePlayer(Player player) {
Object notchEntity = getEntityPlayer(player);
// Save the player too
this.player = player;
if (!hasInitialized) {
// Do this first, in case we encounter an exception
hasInitialized = true;
// Retrieve the server handler
if (serverHandlerField == null) {
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(
"NetServerHandler", MinecraftReflection.getPlayerConnectionClass());
proxyServerField = getProxyField(notchEntity, serverHandlerField);
}
// Yo dawg
serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
serverHandler = serverHandlerRef.getValue();
// Next, get the network manager
if (networkManagerField == null)
networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(
"networkManager", MinecraftReflection.getNetworkManagerClass());
initializeNetworkManager(networkManagerField, serverHandler);
}
}
/**
* Initialize the player injector from a NetLoginHandler.
* @param netLoginHandler - the net login handler to inject.
*/
public void initializeLogin(Object netLoginHandler) {
if (!hasInitialized) {
// Just in case
if (!MinecraftReflection.isLoginHandler(netLoginHandler))
throw new IllegalArgumentException("netLoginHandler (" + netLoginHandler + ") is not a " +
MinecraftReflection.getNetLoginHandlerName());
hasInitialized = true;
loginHandler = netLoginHandler;
if (netLoginNetworkField == null)
netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler).
getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass());
initializeNetworkManager(netLoginNetworkField, netLoginHandler);
}
}
private void initializeNetworkManager(Field reference, Object container) {
networkManagerRef = new VolatileField(reference, container);
networkManager = networkManagerRef.getValue();
// No, don't do it
if (networkManager instanceof Factory) {
return;
}
// Create the network manager modifier from the actual object type
if (networkManager != null && networkModifier == null)
networkModifier = new StructureModifier<Object>(networkManager.getClass(), null, false);
// And the queue method
if (queueMethod == null)
queueMethod = FuzzyReflection.fromClass(reference.getType()).
getMethodByParameters("queue", MinecraftReflection.getPacketClass());
}
/**
* Retrieve whether or not the server handler is a proxy object.
* @return TRUE if it is, FALSE otherwise.
*/
protected boolean hasProxyServerHandler() {
return hasProxyType;
}
/**
* Retrieve the current network manager.
* @return Current network manager.
*/
public Object getNetworkManager() {
return networkManagerRef.getValue();
}
/**
* Retrieve the current server handler (PlayerConnection).
* @return Current server handler.
*/
public Object getServerHandler() {
return serverHandlerRef.getValue();
}
/**
* Set the current network manager.
* @param value - new network manager.
* @param force - whether or not to save this value.
*/
public void setNetworkManager(Object value, boolean force) {
networkManagerRef.setValue(value);
if (force)
networkManagerRef.saveValue();
initializeNetworkManager(networkManagerField, serverHandler);
}
/**
* Retrieve the associated socket of this player.
* @return The associated socket.
* @throws IllegalAccessException If we're unable to read the socket field.
*/
@Override
public Socket getSocket() throws IllegalAccessException {
try {
if (socketField == null)
socketField = FuzzyReflection.fromObject(networkManager, true).
getFieldListByType(Socket.class).get(0);
if (socket == null)
socket = (Socket) FieldUtils.readField(socketField, networkManager, true);
return socket;
} catch (IndexOutOfBoundsException e) {
throw new IllegalAccessException("Unable to read the socket field.");
}
}
/**
* Retrieve the associated remote address of a player.
* @return The associated remote address.
* @throws IllegalAccessException If we're unable to read the socket address field.
*/
@Override
public SocketAddress getAddress() throws IllegalAccessException {
try {
if (socketAddressField == null)
socketAddressField = FuzzyReflection.fromObject(networkManager, true).
getFieldListByType(SocketAddress.class).get(0);
if (socketAddress == null)
socketAddress = (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true);
return socketAddress;
} catch (IndexOutOfBoundsException e) {
// Inform about the state of the network manager too
reporter.reportWarning(
this, Report.newBuilder(REPORT_INVALID_NETWORK_MANAGER).callerParam(networkManager).build());
throw new IllegalAccessException("Unable to read the socket address field.");
}
}
/**
* Attempt to disconnect the current client.
* @param message - the message to display.
* @throws InvocationTargetException If disconnection failed.
*/
@Override
public void disconnect(String message) throws InvocationTargetException {
// Get a non-null handler
boolean usingNetServer = serverHandler != null;
Object handler = usingNetServer ? serverHandler : loginHandler;
Method disconnect = usingNetServer ? serverDisconnect : loginDisconnect;
// Execute disconnect on it
if (handler != null) {
if (disconnect == null) {
try {
disconnect = FuzzyReflection.fromObject(handler).getMethodByName("disconnect.*");
} catch (IllegalArgumentException e) {
// Just assume it's the first String method
disconnect = FuzzyReflection.fromObject(handler).getMethodByParameters("disconnect", String.class);
reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUME_DISCONNECT_METHOD).messageParam(disconnect));
}
// Save the method for later
if (usingNetServer)
serverDisconnect = disconnect;
else
loginDisconnect = disconnect;
}
try {
disconnect.invoke(handler, message);
return;
} catch (IllegalArgumentException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_INVALID_ARGUMENT_DISCONNECT).error(e).messageParam(message).callerParam(handler));
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_ACCESS_DISCONNECT).error(e));
}
}
// Fuck it
try {
Socket socket = getSocket();
try {
socket.close();
} catch (IOException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CLOSE_SOCKET).error(e).callerParam(socket));
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_ACCESS_DENIED_CLOSE_SOCKET).error(e));
}
}
private Field getProxyField(Object notchEntity, Field serverField) {
try {
Object currentHandler = FieldUtils.readField(serverHandlerField, notchEntity, true);
// This is bad
if (currentHandler == null)
throw new ServerHandlerNull();
// See if this isn't a standard net handler class
if (!isStandardMinecraftNetHandler(currentHandler)) {
// This is our proxy object
if (currentHandler instanceof Factory)
return null;
hasProxyType = true;
reporter.reportWarning(this, Report.newBuilder(REPORT_DETECTED_CUSTOM_SERVER_HANDLER).callerParam(serverField));
// No? Is it a Proxy type?
try {
FuzzyReflection reflection = FuzzyReflection.fromObject(currentHandler, true);
// It might be
return reflection.getFieldByType("NetServerHandler", MinecraftReflection.getPlayerConnectionClass());
} catch (RuntimeException e) {
// Damn
}
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PROXY_SERVER_HANDLER).error(e).callerParam(notchEntity, serverField));
}
// Nope, just go with it
return null;
}
/**
* Determine if a given object is a standard Minecraft net handler.
* @param obj the object to test.
* @return TRUE if it is, FALSE otherwise.
*/
private boolean isStandardMinecraftNetHandler(Object obj) {
if (obj == null)
return false;
Class<?> clazz = obj.getClass();
return MinecraftReflection.getNetLoginHandlerClass().equals(clazz) ||
MinecraftReflection.getPlayerConnectionClass().equals(clazz);
}
/**
* Retrieves the current net handler for this player.
* @return Current net handler.
* @throws IllegalAccessException Unable to find or retrieve net handler.
*/
protected Object getNetHandler() throws IllegalAccessException {
return getNetHandler(false);
}
/**
* Retrieves the current net handler for this player.
* @param refresh - Whether or not to refresh
* @return Current net handler.
* @throws IllegalAccessException Unable to find or retrieve net handler.
* @return The current net handler for this player
*/
protected Object getNetHandler(boolean refresh) throws IllegalAccessException {
// What a mess
try {
if (netHandlerField == null)
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType("NetHandler", MinecraftReflection.getNetHandlerClass());
} catch (RuntimeException e1) {
// Swallow it
}
// Second attempt
if (netHandlerField == null) {
try {
// Well, that sucks. Try just Minecraft objects then.
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType(MinecraftReflection.getMinecraftObjectRegex());
} catch (RuntimeException e2) {
throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
}
}
// Get the handler
if (netHandler == null || refresh)
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
return netHandler;
}
/**
* Retrieve the stored entity player from a given NetHandler.
* @param netHandler - the nethandler to retrieve it from.
* @return The stored entity player.
* @throws IllegalAccessException If the reflection failed.
*/
private Object getEntityPlayer(Object netHandler) throws IllegalAccessException {
if (entityPlayerField == null)
entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType(
"EntityPlayer", MinecraftReflection.getEntityPlayerClass());
return FieldUtils.readField(entityPlayerField, netHandler);
}
/**
* Processes the given packet as if it was transmitted by the current player.
* @param packet - packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
public void processPacket(Object packet) throws IllegalAccessException, InvocationTargetException {
Object netHandler = getNetHandler();
// Get the process method
if (processMethod == null) {
try {
processMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
getMethodByParameters("processPacket", netHandlerField.getType());
} catch (RuntimeException e) {
throw new IllegalArgumentException("Cannot locate process packet method: " + e.getMessage());
}
}
// We're ready
try {
processMethod.invoke(packet, netHandler);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Method " + processMethod.getName() + " is not compatible.");
} catch (InvocationTargetException e) {
throw e;
}
}
/**
* Send a packet to the client.
* @param packet - server packet to send.
* @param marker - the network marker.
* @param filtered - whether or not the packet will be filtered by our listeners.
* @throws InvocationTargetException If an error occured when sending the packet.
*/
@Override
public abstract void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException;
/**
* Inject a hook to catch packets sent to the current player.
*/
public abstract void injectManager();
/**
* Remove all hooks and modifications.
*/
public final void cleanupAll() {
if (!clean) {
cleanHook();
writePacketInterceptor.cleanup();
}
clean = true;
}
/**
* Clean up after the player has disconnected.
*/
public abstract void handleDisconnect();
/**
* Override to add custom cleanup behavior.
*/
protected abstract void cleanHook();
/**
* Determine whether or not this hook has already been cleaned.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClean() {
return clean;
}
/**
* Determine if this inject method can even be attempted.
* @param state - Game phase
* @return TRUE if can be attempted, though possibly with failure, FALSE otherwise.
*/
public abstract boolean canInject(GamePhase state);
/**
* Retrieve the hook type this class represents.
* @return Hook type this class represents.
*/
public abstract PlayerInjectHooks getHookType();
/**
* Invoked before a new listener is registered.
* <p>
* The player injector should only return a non-null value if some or all of the packet IDs are unsupported.
* @param version - the current Minecraft version, or NULL if unknown.
* @param listener - the listener that is about to be registered.
* @return A error message with the unsupported packet IDs, or NULL if this listener is valid.
*/
public abstract UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener);
/**
* Allows a packet to be sent by the listeners.
* @param packet - packet to sent.
* @return The given packet, or the packet replaced by the listeners.
*/
@SuppressWarnings("deprecation")
public Object handlePacketSending(Object packet) {
try {
// Get the packet ID too
Integer id = invoker.getPacketID(packet);
Player currentPlayer = player;
// Hack #1
if (updateOnLogin) {
if (updatedPlayer == null) {
try {
final Object handler = getNetHandler(true);
// Is this a net server class?
if (MinecraftReflection.getPlayerConnectionClass().isAssignableFrom(handler.getClass())) {
setUpdatedPlayer(
(Player) MinecraftReflection.getBukkitEntity(getEntityPlayer(handler))
);
}
} catch (IllegalAccessException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLAYER).error(e).callerParam(packet));
}
}
// This will only occur in the NetLoginHandler injection
if (updatedPlayer != null) {
currentPlayer = updatedPlayer;
updateOnLogin = false;
}
}
// Make sure we're listening
if (id != null && hasListener(id)) {
NetworkMarker marker = queuedMarkers.remove(packet);
// A packet has been sent guys!
PacketType type = PacketType.findLegacy(id, Sender.SERVER);
PacketContainer container = new PacketContainer(type, packet);
PacketEvent event = PacketEvent.fromServer(invoker, container, marker, currentPlayer);
invoker.invokePacketSending(event);
// Cancelling is pretty simple. Just ignore the packet.
if (event.isCancelled())
return null;
// Right, remember to replace the packet again
Object result = event.getPacket().getHandle();
marker = NetworkMarker.getNetworkMarker(event);
// See if we need to proxy the write method
if (result != null && (NetworkMarker.hasOutputHandlers(marker) || NetworkMarker.hasPostListeners(marker))) {
result = writePacketInterceptor.constructProxy(result, event, marker);
}
return result;
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_HANDLE_PACKET).error(e).callerParam(packet));
}
return packet;
}
/**
* Determine if the given injector is listening for this packet ID.
* @param packetID - packet ID to check.
* @return TRUE if it is, FALSE oterhwise.
*/
protected abstract boolean hasListener(int packetID);
/**
* Retrieve the current player's input stream.
* @param cache - whether or not to cache the result of this method.
* @return The player's input stream.
*/
public DataInputStream getInputStream(boolean cache) {
// And the data input stream that we'll use to identify a player
if (networkManager == null)
throw new IllegalStateException("Network manager is NULL.");
if (inputField == null)
inputField = FuzzyReflection.fromObject(networkManager, true).
getFieldByType("java\\.io\\.DataInputStream");
// Get the associated input stream
try {
if (cache && cachedInput != null)
return cachedInput;
// Save to cache
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager, true);
return cachedInput;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read input stream.", e);
}
}
/**
* Retrieve the hooked player.
*/
@Override
public Player getPlayer() {
return player;
}
/**
* Set the hooked player.
* <p>
* Should only be called during the creation of the injector.
* @param player - the new hooked player.
*/
public void setPlayer(Player player) {
this.player = player;
}
/**
* Object that can invoke the packet events.
* @return Packet event invoker.
*/
public ListenerInvoker getInvoker() {
return invoker;
}
/**
* Retrieve the hooked player object OR the more up-to-date player instance.
* @return The hooked player, or a more up-to-date instance.
*/
@Override
public Player getUpdatedPlayer() {
if (updatedPlayer != null)
return updatedPlayer;
else
return player;
}
@Override
public void transferState(SocketInjector delegate) {
// Do nothing
}
@Override
public void setUpdatedPlayer(Player updatedPlayer) {
this.updatedPlayer = updatedPlayer;
}
/**
* Indicates that a player's NetServerHandler or PlayerConnection was NULL.
* <p>
* This is usually because the player has just logged out, or due to it being a "fake" player in MCPC+/Cauldron.
* @author Kristian
*/
public static class ServerHandlerNull extends IllegalAccessError {
private static final long serialVersionUID = 1L;
public ServerHandlerNull() {
super("Unable to fetch server handler: was NUll.");
}
public ServerHandlerNull(String s) {
super(s);
}
}
}
@@ -1,143 +0,0 @@
package com.comphenix.protocol.injector.player;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
/**
* Constructor for different player injectors.
*
* @author Kristian
*/
public class PlayerInjectorBuilder {
public static PlayerInjectorBuilder newBuilder() {
return new PlayerInjectorBuilder();
}
protected PlayerInjectorBuilder() {
// Use the static method.
}
protected ErrorReporter reporter;
protected Predicate<GamePhase> injectionFilter;
protected ListenerInvoker invoker;
protected Set<PacketListener> packetListeners;
protected Server server;
protected MinecraftVersion version;
/**
* The error reporter used by the created injector.
* @param reporter - new error reporter.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
this.reporter = reporter;
return this;
}
/**
* The injection filter that is used to determine if it is necessary to perform
* injection during a certain phase.
* @param injectionFilter - filter predicate.
* @return This builder, for chaining.
*/
@Nonnull
public PlayerInjectorBuilder injectionFilter(@Nonnull Predicate<GamePhase> injectionFilter) {
Preconditions.checkNotNull(injectionFilter, "injectionFilter cannot be NULL");
this.injectionFilter = injectionFilter;
return this;
}
/**
* The packet stream invoker.
* @param invoker - the invoker.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
this.invoker = invoker;
return this;
}
/**
* Set the set of packet listeners.
* @param packetListeners - packet listeners.
* @return This builder, for chaining.
*/
@Nonnull
public PlayerInjectorBuilder packetListeners(@Nonnull Set<PacketListener> packetListeners) {
Preconditions.checkNotNull(packetListeners, "packetListeners cannot be NULL");
this.packetListeners = packetListeners;
return this;
}
/**
* Set the Bukkit server used for scheduling.
* @param server - the Bukkit server.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder server(@Nonnull Server server) {
Preconditions.checkNotNull(server, "server cannot be NULL");
this.server = server;
return this;
}
/**
* Set the current Minecraft version.
* @param version - the current Minecraft version, or NULL if unknown.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder version(MinecraftVersion version) {
this.version = version;
return this;
}
/**
* Called before an object is created with this builder.
*/
private void initializeDefaults() {
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
// Initialize with default values if we can
if (reporter == null)
reporter = ProtocolLibrary.getErrorReporter();
if (invoker == null)
invoker = (PacketFilterManager) manager;
if (server == null)
server = Bukkit.getServer();
if (injectionFilter == null)
throw new IllegalStateException("injectionFilter must be initialized.");
if (packetListeners == null)
throw new IllegalStateException("packetListeners must be initialized.");
}
/**
* Construct the injection handler.
* <p>
* Any builder parameter marked as NON-NULL is essential and must be initialized.
* @return The constructed injection handler using the current parameters.
*/
public PlayerInjectionHandler buildHandler() {
// Fill any default fields
initializeDefaults();
return new ProxyPlayerInjectionHandler(
reporter, injectionFilter,
invoker, packetListeners, server, version);
}
}
@@ -1,762 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.io.DataInputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.compat.netty.WrappedChannel;
import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.injector.server.BukkitSocketInjector;
import com.comphenix.protocol.injector.server.InputStreamLookupBuilder;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.SafeCacheBuilder;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
/**
* Responsible for injecting into a player's sendPacket method.
*
* @author Kristian
*/
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
// Warnings and errors
public static final ReportType REPORT_UNSUPPPORTED_LISTENER = new ReportType("Cannot fully register listener for %s: %s");
// Fallback to older player hook types
public static final ReportType REPORT_PLAYER_HOOK_FAILED = new ReportType("Player hook %s failed.");
public static final ReportType REPORT_SWITCHED_PLAYER_HOOK = new ReportType("Switching to %s instead.");
public static final ReportType REPORT_HOOK_CLEANUP_FAILED = new ReportType("Cleaing up after player hook failed.");
public static final ReportType REPORT_CANNOT_REVERT_HOOK = new ReportType("Unable to fully revert old injector. May cause conflicts.");
// Server connection injection
private InjectedServerConnection serverInjection;
// Server socket injection
private AbstractInputStreamLookup inputStreamLookup;
// NetLogin injector
private NetLoginInjector netLoginInjector;
// The last successful player hook
private WeakReference<PlayerInjector> lastSuccessfulHook;
// Dummy injection
private ConcurrentMap<Player, PlayerInjector> dummyInjectors =
SafeCacheBuilder.newBuilder().
expireAfterWrite(30, TimeUnit.SECONDS).
build(BlockingHashMap.<Player, PlayerInjector>newInvalidCacheLoader());
// Player injection
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
// Player injection types
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// Error reporter
private ErrorReporter reporter;
// Whether or not we're closing
private boolean hasClosed;
// Used to invoke events
private ListenerInvoker invoker;
// Current Minecraft version
private MinecraftVersion version;
// Enabled packet filters
private IntegerSet sendingFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
// List of packet listeners
private Set<PacketListener> packetListeners;
// Used to filter injection attempts
private Predicate<GamePhase> injectionFilter;
public ProxyPlayerInjectionHandler(
ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server, MinecraftVersion version) {
this.reporter = reporter;
this.invoker = invoker;
this.injectionFilter = injectionFilter;
this.packetListeners = packetListeners;
this.version = version;
this.inputStreamLookup = InputStreamLookupBuilder.newBuilder().
server(server).
reporter(reporter).
build();
// Create net login injectors and the server connection injector
this.netLoginInjector = new NetLoginInjector(reporter, server, this);
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
serverInjection.injectList();
}
@Override
public int getProtocolVersion(Player player) {
// Just use the server version
return MinecraftProtocolVersion.getCurrentVersion();
}
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
@Override
public PlayerInjectHooks getPlayerHook() {
return getPlayerHook(GamePhase.PLAYING);
}
/**
* Retrieves how the server packets are read.
* @param phase - the current game phase.
* @return Injection method for reading server packets.
*/
@Override
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
switch (phase) {
case LOGIN:
return loginPlayerHook;
case PLAYING:
return playingPlayerHook;
default:
throw new IllegalArgumentException("Cannot retrieve injection hook for both phases at the same time.");
}
}
@Override
public boolean hasMainThreadListener(PacketType type) {
return sendingFilters.contains(type.getLegacyId());
}
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
setPlayerHook(GamePhase.PLAYING, playerHook);
}
/**
* Sets how the server packets are read.
* @param phase - the current game phase.
* @param playerHook - the new injection method for reading server packets.
*/
@Override
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
if (phase.hasLogin())
loginPlayerHook = playerHook;
if (phase.hasPlaying())
playingPlayerHook = playerHook;
// Make sure the current listeners are compatible
checkListener(packetListeners);
}
@Override
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
sendingFilters.add(type.getLegacyId());
}
@Override
public void removePacketHandler(PacketType type) {
sendingFilters.remove(type.getLegacyId());
}
/**
* Used to construct a player hook.
* @param player - the player to hook.
* @param hook - the hook type.
* @return A new player hoook
* @throws IllegalAccessException Unable to do our reflection magic.
*/
private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
// Construct the correct player hook
switch (hook) {
case NETWORK_HANDLER_FIELDS:
return new NetworkFieldInjector(reporter, player, invoker, sendingFilters);
case NETWORK_MANAGER_OBJECT:
return new NetworkObjectInjector(reporter, player, invoker, sendingFilters);
case NETWORK_SERVER_OBJECT:
return new NetworkServerInjector(reporter, player, invoker, sendingFilters, serverInjection);
default:
throw new IllegalArgumentException("Cannot construct a player injector.");
}
}
/**
* Retrieve a player by its DataInput connection.
* @param inputStream - the associated DataInput connection.
* @return The player we found.
*/
@Override
public Player getPlayerByConnection(DataInputStream inputStream) {
// Wait until the connection owner has been established
SocketInjector injector = inputStreamLookup.waitSocketInjector(inputStream);
if (injector != null) {
return injector.getPlayer();
} else {
return null;
}
}
/**
* Helper function that retrieves the injector type of a given player injector.
* @param injector - injector type.
* @return The injector type.
*/
private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
}
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This call will be ignored if there's no listener that can receive the given events.
* @param player - player to hook.
* @param strategy - how to handle previous player injections.
*/
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
// Inject using the player instance itself
if (isInjectionNecessary(GamePhase.PLAYING)) {
injectPlayer(player, player, strategy, GamePhase.PLAYING);
}
}
/**
* Determine if it's truly necessary to perform the given player injection.
* @param phase - current game phase.
* @return TRUE if we should perform the injection, FALSE otherwise.
*/
public boolean isInjectionNecessary(GamePhase phase) {
return injectionFilter.apply(phase);
}
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This method will always perform the instructed injection.
*
* @param player - player to hook.
* @param injectionPoint - the object to use during the injection process.
* @param phase - the current game phase.
* @return The resulting player injector, or NULL if the injection failed.
*/
PlayerInjector injectPlayer(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) {
if (player == null)
throw new IllegalArgumentException("Player cannot be NULL.");
if (injectionPoint == null)
throw new IllegalArgumentException("injectionPoint cannot be NULL.");
if (phase == null)
throw new IllegalArgumentException("phase cannot be NULL.");
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
synchronized (player) {
return injectPlayerInternal(player, injectionPoint, stategy, phase);
}
}
// Unsafe variant of the above
private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) {
PlayerInjector injector = playerInjection.get(player);
PlayerInjectHooks tempHook = getPlayerHook(phase);
PlayerInjectHooks permanentHook = tempHook;
// The given player object may be fake, so be careful!
// See if we need to inject something else
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
// Don't inject if the class has closed
if (!hasClosed && (tempHook != getInjectorType(injector) || invalidInjector)) {
while (tempHook != PlayerInjectHooks.NONE) {
// Whether or not the current hook method failed completely
boolean hookFailed = false;
// Remove the previous hook, if any
cleanupHook(injector);
try {
injector = getHookInstance(player, tempHook);
// Make sure this injection method supports the current game phase
if (injector.canInject(phase)) {
injector.initialize(injectionPoint);
// Get socket and socket injector
SocketAddress address = injector.getAddress();
// Ignore logged out players
if (address == null)
return null;
SocketInjector previous = inputStreamLookup.peekSocketInjector(address);
Socket socket = injector.getSocket();
// Close any previously associated hooks before we proceed
if (previous != null && !(player instanceof Factory)) {
switch (stategy) {
case OVERRIDE:
uninjectPlayer(previous.getPlayer(), true);
break;
case BAIL_OUT:
return null;
}
}
injector.injectManager();
saveAddressLookup(address, socket, injector);
break;
}
} catch (PlayerLoggedOutException e) {
throw e;
} catch (Exception e) {
// Mark this injection attempt as a failure
reporter.reportDetailed(this,
Report.newBuilder(REPORT_PLAYER_HOOK_FAILED).messageParam(tempHook).callerParam(player, injectionPoint, phase).error(e)
);
hookFailed = true;
}
// Choose the previous player hook type
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
if (hookFailed)
reporter.reportWarning(this, Report.newBuilder(REPORT_SWITCHED_PLAYER_HOOK).messageParam(tempHook));
// Check for UTTER FAILURE
if (tempHook == PlayerInjectHooks.NONE) {
cleanupHook(injector);
injector = null;
hookFailed = true;
}
// Should we set the default hook method too?
if (hookFailed) {
permanentHook = tempHook;
}
}
// Update values
if (injector != null)
lastSuccessfulHook = new WeakReference<PlayerInjector>(injector);
if (permanentHook != getPlayerHook(phase))
setPlayerHook(phase, tempHook);
// Save injector
if (injector != null) {
playerInjection.put(player, injector);
}
}
return injector;
}
private void saveAddressLookup(SocketAddress address, Socket socket, SocketInjector injector) {
SocketAddress socketAddress = socket != null ? socket.getRemoteSocketAddress() : null;
if (socketAddress != null && !Objects.equal(socketAddress, address)) {
// Save this version as well
inputStreamLookup.setSocketInjector(socketAddress, injector);
}
// Save injector
inputStreamLookup.setSocketInjector(address, injector);
}
private void cleanupHook(PlayerInjector injector) {
// Clean up as much as possible
try {
if (injector != null)
injector.cleanupAll();
} catch (Exception ex) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_HOOK_CLEANUP_FAILED).callerParam(injector).error(ex));
}
}
/**
* Invoke special routines for handling disconnect before a player is uninjected.
* @param player - player to process.
*/
@Override
public void handleDisconnect(Player player) {
PlayerInjector injector = getInjector(player);
if (injector != null) {
injector.handleDisconnect();
}
}
@Override
public void updatePlayer(Player player) {
SocketAddress address = player.getAddress();
// Ignore logged out players
if (address != null) {
SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
if (injector != null) {
injector.setUpdatedPlayer(player);
} else {
inputStreamLookup.setSocketInjector(player.getAddress(),
new BukkitSocketInjector(player));
}
}
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
@Override
public boolean uninjectPlayer(Player player) {
return uninjectPlayer(player, false);
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @param prepareNextHook - whether or not we need to fix any lingering hooks.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
private boolean uninjectPlayer(Player player, boolean prepareNextHook) {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.remove(player);
if (injector != null) {
injector.cleanupAll();
// Remove the "hooked" network manager in our instance as well
if (prepareNextHook && injector instanceof NetworkObjectInjector) {
try {
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
dummyInjector.initializePlayer(player);
dummyInjector.setNetworkManager(injector.getNetworkManager(), true);
} catch (IllegalAccessException e) {
// Let the user know
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REVERT_HOOK).error(e));
}
}
return true;
}
}
return false;
}
/**
* Unregisters a player by the given address.
* <p>
* If the server handler has been created before we've gotten a chance to unject the player,
* the method will try a workaround to remove the injected hook in the NetServerHandler.
*
* @param address - address of the player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
if (!hasClosed && address != null) {
SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
// Clean up
if (injector != null)
uninjectPlayer(injector.getPlayer(), true);
return true;
}
return false;
}
/**
* Send the given packet to the given receiver.
* @param receiver - the player receiver.
* @param packet - the packet to send.
* @param filters - whether or not to invoke the packet filters.
* @throws InvocationTargetException If an error occured during sending.
*/
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
SocketInjector injector = getInjector(receiver);
// Send the packet, or drop it completely
if (injector != null) {
injector.sendServerPacket(packet.getHandle(), marker, filters);
} else {
throw new PlayerLoggedOutException(String.format(
"Unable to send packet %s (%s): Player %s has logged out.",
packet.getType(), packet, receiver
));
}
}
/**
* Recieve a packet as if it were sent by the given player.
* @param player - the sender.
* @param mcPacket - the packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
@Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
PlayerInjector injector = getInjector(player);
// Process the given packet, or simply give up
if (injector != null)
injector.processPacket(mcPacket);
else
throw new PlayerLoggedOutException(String.format(
"Unable to receieve packet %s. Player %s has logged out.",
mcPacket, player
));
}
/**
* Retrieve the injector associated with this player.
* @param player - the player to find.
* @return The injector, or NULL if not found.
*/
private PlayerInjector getInjector(Player player) {
PlayerInjector injector = playerInjection.get(player);
if (injector == null) {
// Try getting it from the player itself
SocketAddress address = player.getAddress();
// Must have logged out - there's nothing we can do
if (address == null)
return null;
// Look that up without blocking
SocketInjector result = inputStreamLookup.peekSocketInjector(address);
// Ensure that it is non-null and a player injector
if (result instanceof PlayerInjector)
return (PlayerInjector) result;
else
// Make a dummy injector them
return createDummyInjector(player);
} else {
return injector;
}
}
/**
* Construct a simple dummy injector incase none has been constructed.
* @param player - the CraftPlayer to construct for.
* @return A dummy injector, or NULL if the given player is not a CraftPlayer.
*/
private PlayerInjector createDummyInjector(Player player) {
if (!MinecraftReflection.getCraftPlayerClass().isAssignableFrom(player.getClass())) {
// No - this is not safe
return null;
}
try {
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
dummyInjector.initializePlayer(player);
// This probably means the player has disconnected
if (dummyInjector.getSocket() == null) {
return null;
}
inputStreamLookup.setSocketInjector(dummyInjector.getAddress(), dummyInjector);
dummyInjectors.put(player, dummyInjector);
return dummyInjector;
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access fields.", e);
}
}
/**
* Retrieve a player injector by looking for its NetworkManager.
* @param networkManager - current network manager.
* @return Related player injector.
*/
PlayerInjector getInjectorByNetworkHandler(Object networkManager) {
// That's not legal
if (networkManager == null)
return null;
// O(n) is okay in this instance. This is only a backup solution.
for (PlayerInjector injector : playerInjection.values()) {
if (injector.getNetworkManager() == networkManager)
return injector;
}
// None found
return null;
}
@Override
public boolean canRecievePackets() {
return false;
}
@Override
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
throw new UnsupportedOperationException("Proxy injection cannot handle received packets.");
}
/**
* Determine if the given listeners are valid.
* @param listeners - listeners to check.
*/
@Override
public void checkListener(Set<PacketListener> listeners) {
// Make sure the current listeners are compatible
if (getLastSuccessfulHook() != null) {
for (PacketListener listener : listeners) {
checkListener(listener);
}
}
}
/**
* Retrieve the last successful hook.
* <p>
* May be NULL if the hook has been uninjected.
* @return Last successful hook.
*/
private PlayerInjector getLastSuccessfulHook() {
return lastSuccessfulHook != null ? lastSuccessfulHook.get() : null;
}
/**
* Determine if a listener is valid or not.
* <p>
* If not, a warning will be printed to the console.
* @param listener - listener to check.
*/
@Override
public void checkListener(PacketListener listener) {
PlayerInjector last = getLastSuccessfulHook();
if (last != null) {
UnsupportedListener result = last.checkListener(version, listener);
// We won't prevent the listener, as it may still have valid packets
if (result != null) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNSUPPPORTED_LISTENER).messageParam(PacketAdapter.getPluginName(listener), result)
);
// These are illegal
for (int packetID : result.getPackets()) {
removePacketHandler(PacketType.findLegacy(packetID, Sender.CLIENT));
removePacketHandler(PacketType.findLegacy(packetID, Sender.SERVER));
}
}
}
}
/**
* Retrieve the current list of registered sending listeners.
* @return List of the sending listeners's packet IDs.
*/
@Override
public Set<PacketType> getSendingFilters() {
return PacketRegistry.toPacketTypes(sendingFilters.toSet(), Sender.SERVER);
}
@Override
public void close() {
// Guard
if (hasClosed || playerInjection == null)
return;
// Remove everything
for (PlayerInjector injection : playerInjection.values()) {
if (injection != null) {
injection.cleanupAll();
}
}
// Remove server handler
if (inputStreamLookup != null)
inputStreamLookup.cleanupAll();
if (serverInjection != null)
serverInjection.cleanupAll();
if (netLoginInjector != null)
netLoginInjector.cleanupAll();
inputStreamLookup = null;
serverInjection = null;
netLoginInjector = null;
hasClosed = true;
playerInjection.clear();
invoker = null;
}
@Override
public WrappedChannel getChannel(Player player) {
throw new UnsupportedOperationException();
}
}
@@ -1,370 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import com.google.common.base.Objects;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
/**
* Represents an array list that wraps another list, while automatically replacing one element with another.
* <p>
* The replaced elements can be recovered.
*
* @author Kristian
* @param <TKey> - type of the elements we're replacing.
*/
class ReplacedArrayList<TKey> extends ArrayList<TKey> {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 1008492765999744804L;
private BiMap<TKey, TKey> replaceMap = HashBiMap.create();
private List<TKey> underlyingList;
public ReplacedArrayList(List<TKey> underlyingList) {
this.underlyingList = underlyingList;
}
/**
* Invoked when a element inserted is replaced.
* @param inserting - the element inserted.
* @param replacement - the element that it should replace.
*/
protected void onReplacing(TKey inserting, TKey replacement) {
// Default is to do nothing.
}
/**
* Invoked when an element is being inserted.
* <p>
* This should be used to add a "replace" map.
* @param inserting - the element to insert.
*/
protected void onInserting(TKey inserting) {
// Default is again nothing
}
/**
* Invoked when an element is being removed.
* @param removing - the element being removed.
*/
protected void onRemoved(TKey removing) {
// Do nothing
}
@Override
public boolean add(TKey element) {
onInserting(element);
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
return delegate().add(replacement);
} else {
return delegate().add(element);
}
}
@Override
public void add(int index, TKey element) {
onInserting(element);
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
delegate().add(index, replacement);
} else {
delegate().add(index, element);
}
}
@Override
public boolean addAll(Collection<? extends TKey> collection) {
int oldSize = size();
for (TKey element : collection)
add(element);
return size() != oldSize;
}
@Override
public boolean addAll(int index, Collection<? extends TKey> elements) {
int oldSize = size();
for (TKey element : elements)
add(index++, element);
return size() != oldSize;
}
@SuppressWarnings("unchecked")
@Override
public boolean remove(Object object) {
boolean success = delegate().remove(object);
if (success)
onRemoved((TKey) object);
return success;
}
@Override
public TKey remove(int index) {
TKey removed = delegate().remove(index);
if (removed != null)
onRemoved(removed);
return removed;
}
@Override
public boolean removeAll(Collection<?> collection) {
int oldSize = size();
// Use the remove method
for (Object element : collection)
remove(element);
return size() != oldSize;
}
protected List<TKey> delegate() {
return underlyingList;
}
@Override
public void clear() {
for (TKey element : delegate())
onRemoved(element);
delegate().clear();
}
@Override
public boolean contains(Object o) {
return delegate().contains(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return delegate().containsAll(c);
}
@Override
public TKey get(int index) {
return delegate().get(index);
}
@Override
public int indexOf(Object o) {
return delegate().indexOf(o);
}
@Override
public boolean isEmpty() {
return delegate().isEmpty();
}
@Override
public Iterator<TKey> iterator() {
return delegate().iterator();
}
@Override
public int lastIndexOf(Object o) {
return delegate().lastIndexOf(o);
}
@Override
public ListIterator<TKey> listIterator() {
return delegate().listIterator();
}
@Override
public ListIterator<TKey> listIterator(int index) {
return delegate().listIterator(index);
}
@Override
public boolean retainAll(Collection<?> c) {
int oldSize = size();
for (Iterator<TKey> it = delegate().iterator(); it.hasNext(); ) {
TKey current = it.next();
// Remove elements that are not in the list
if (!c.contains(current)) {
it.remove();
onRemoved(current);
}
}
return size() != oldSize;
}
@Override
public TKey set(int index, TKey element) {
// Make sure to replace the element
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
return delegate().set(index, replacement);
} else {
return delegate().set(index, element);
}
}
@Override
public int size() {
return delegate().size();
}
@Override
public List<TKey> subList(int fromIndex, int toIndex) {
return delegate().subList(fromIndex, toIndex);
}
@Override
public Object[] toArray() {
return delegate().toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return delegate().toArray(a);
}
/**
* Add a replace rule.
* <p>
* This automatically replaces every existing element.
* @param target - instance to find.
* @param replacement - instance to replace with.
*/
public synchronized void addMapping(TKey target, TKey replacement) {
addMapping(target, replacement, false);
}
/**
* Retrieve the old value, if it exists.
* @param target - the key.
* @return The value that was replaced, or NULL.
*/
public TKey getMapping(TKey target) {
return replaceMap.get(target);
}
/**
* Add a replace rule.
* <p>
* This automatically replaces every existing element.
* @param target - instance to find.
* @param replacement - instance to replace with.
* @param ignoreExisting - whether or not to ignore the existing elements.
*/
public synchronized void addMapping(TKey target, TKey replacement, boolean ignoreExisting) {
replaceMap.put(target, replacement);
// Replace existing elements
if (!ignoreExisting) {
replaceAll(target, replacement);
}
}
/**
* Revert the given mapping.
* @param target - the instance we replaced.
* @return The old mapped value, or NULL if nothing was replaced.
*/
public synchronized TKey removeMapping(TKey target) {
// Make sure the mapping exist
if (replaceMap.containsKey(target)) {
TKey replacement = replaceMap.get(target);
replaceMap.remove(target);
// Revert existing elements
replaceAll(replacement, target);
return replacement;
}
return null;
}
/**
* Swap the new replaced value with its old value.
* @param target - the instance we replaced.
* @param The old mapped value, or NULL if nothing was swapped.
*/
public synchronized TKey swapMapping(TKey target) {
// Make sure the mapping exist
TKey replacement = removeMapping(target);
// Add the reverse
if (replacement != null) {
replaceMap.put(replacement, target);
}
return replacement;
}
/**
* Replace all instances of the given object.
* @param find - object to find.
* @param replace - object to replace it with.
*/
public synchronized void replaceAll(TKey find, TKey replace) {
for (int i = 0; i < underlyingList.size(); i++) {
if (Objects.equal(underlyingList.get(i), find)) {
onReplacing(find, replace);
underlyingList.set(i, replace);
}
}
}
/**
* Undo all replacements.
*/
public synchronized void revertAll() {
// No need to do anything else
if (replaceMap.size() < 1)
return;
BiMap<TKey, TKey> inverse = replaceMap.inverse();
for (int i = 0; i < underlyingList.size(); i++) {
TKey replaced = underlyingList.get(i);
if (inverse.containsKey(replaced)) {
TKey original = inverse.get(replaced);
onReplacing(replaced, original);
underlyingList.set(i, original);
}
}
replaceMap.clear();
}
@Override
protected void finalize() throws Throwable {
revertAll();
super.finalize();
}
}
@@ -1,64 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.player;
import java.util.Arrays;
import com.google.common.base.Joiner;
/**
* Represents an error message from a player injector.
*
* @author Kristian
*/
class UnsupportedListener {
private String message;
private int[] packets;
/**
* Create a new error message.
* @param message - the message.
* @param packets - unsupported packets.
*/
public UnsupportedListener(String message, int[] packets) {
super();
this.message = message;
this.packets = packets;
}
/**
* Retrieve the error message.
* @return Error message.
*/
public String getMessage() {
return message;
}
/**
* Retrieve all unsupported packets.
* @return Unsupported packets.
*/
public int[] getPackets() {
return packets;
}
@Override
public String toString() {
return String.format("%s (%s)", message, Joiner.on(", ").join(Arrays.asList(packets)));
}
}
@@ -1,83 +0,0 @@
package com.comphenix.protocol.injector.server;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketAddress;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter;
public abstract class AbstractInputStreamLookup {
// Error reporter
protected final ErrorReporter reporter;
// Reference to the server itself
protected final Server server;
protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) {
this.reporter = reporter;
this.server = server;
}
/**
* Inject the given server thread or dedicated connection.
* @param container - class that contains a ServerSocket field.
*/
public abstract void inject(Object container);
/**
* Retrieve the associated socket injector for a player.
* @param input - the indentifying filtered input stream.
* @return The socket injector we have associated with this player.
*/
public abstract SocketInjector waitSocketInjector(InputStream input);
/**
* Retrieve an injector by its socket.
* @param socket - the socket.
* @return The socket injector.
*/
public abstract SocketInjector waitSocketInjector(Socket socket);
/**
* Retrieve a injector by its address.
* @param address - the address of the socket.
* @return The socket injector, or NULL if not found.
*/
public abstract SocketInjector waitSocketInjector(SocketAddress address);
/**
* Attempt to get a socket injector without blocking the thread.
* @param address - the address to lookup.
* @return The socket injector, or NULL if not found.
*/
public abstract SocketInjector peekSocketInjector(SocketAddress address);
/**
* Associate a given socket address to the provided socket injector.
* @param address - the socket address to associate.
* @param injector - the injector.
*/
public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
/**
* If a player can hold a reference to its parent injector, this method will update that reference.
* @param previous - the previous injector.
* @param current - the new injector.
*/
protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
Player player = previous.getPlayer();
// Default implementation
if (player instanceof InjectorContainer) {
TemporaryPlayerFactory.setInjectorInPlayer(player, current);
}
}
/**
* Invoked when the injection should be undone.
*/
public abstract void cleanupAll();
}
@@ -1,83 +0,0 @@
package com.comphenix.protocol.injector.server;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.NetworkMarker;
public class BukkitSocketInjector implements SocketInjector {
private Player player;
// Queue of server packets
private List<QueuedSendPacket> syncronizedQueue = Collections.synchronizedList(new ArrayList<QueuedSendPacket>());
/**
* Represents a temporary socket injector.
* @param player - a temporary player.
*/
public BukkitSocketInjector(Player player) {
if (player == null)
throw new IllegalArgumentException("Player cannot be NULL.");
this.player = player;
}
@Override
public Socket getSocket() throws IllegalAccessException {
throw new UnsupportedOperationException("Cannot get socket from Bukkit player.");
}
@Override
public SocketAddress getAddress() throws IllegalAccessException {
return player.getAddress();
}
@Override
public void disconnect(String message) throws InvocationTargetException {
player.kickPlayer(message);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered)
throws InvocationTargetException {
QueuedSendPacket command = new QueuedSendPacket(packet, marker, filtered);
// Queue until we can find something better
syncronizedQueue.add(command);
}
@Override
public Player getPlayer() {
return player;
}
@Override
public Player getUpdatedPlayer() {
return player;
}
@Override
public void transferState(SocketInjector delegate) {
// Transmit all queued packets to a different injector.
try {
synchronized(syncronizedQueue) {
for (QueuedSendPacket command : syncronizedQueue) {
delegate.sendServerPacket(command.getPacket(), command.getMarker(), command.isFiltered());
}
syncronizedQueue.clear();
}
} catch (InvocationTargetException e) {
throw new RuntimeException("Unable to transmit packets to " + delegate + " from old injector.", e);
}
}
@Override
public void setUpdatedPlayer(Player updatedPlayer) {
this.player = updatedPlayer;
}
}
@@ -1,21 +0,0 @@
package com.comphenix.protocol.injector.server;
/**
* Able to store a socket injector.
* <p>
* A necessary hack.
* @author Kristian
*/
class InjectorContainer {
private volatile SocketInjector injector;
public SocketInjector getInjector() {
return injector;
}
public void setInjector(SocketInjector injector) {
if (injector == null)
throw new IllegalArgumentException("Injector cannot be NULL.");
this.injector = injector;
}
}
@@ -1,47 +0,0 @@
package com.comphenix.protocol.injector.server;
import org.bukkit.Server;
import com.comphenix.protocol.error.ErrorReporter;
/**
* Constructs the appropriate input stream lookup for the current JVM and architecture.
*
* @author Kristian
*/
public class InputStreamLookupBuilder {
public static InputStreamLookupBuilder newBuilder() {
return new InputStreamLookupBuilder();
}
protected InputStreamLookupBuilder() {
// Use the static method.
}
private Server server;
private ErrorReporter reporter;
/**
* Set the server instance to use.
* @param server - server instance.
* @return The current builder, for chaining.
*/
public InputStreamLookupBuilder server(Server server) {
this.server = server;
return this;
}
/**
* Set the error reporter to pass on to the lookup.
* @param reporter - the error reporter.
* @return The current builder, for chaining.
*/
public InputStreamLookupBuilder reporter(ErrorReporter reporter) {
this.reporter = reporter;
return this;
}
public AbstractInputStreamLookup build() {
return new InputStreamReflectLookup(reporter, server);
}
}
@@ -1,186 +0,0 @@
package com.comphenix.protocol.injector.server;
import java.io.FilterInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.bukkit.Server;
import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.collect.MapMaker;
class InputStreamReflectLookup extends AbstractInputStreamLookup {
// Used to access the inner input stream of a filtered input stream
private static Field filteredInputField;
// The default lookup timeout
private static final long DEFAULT_TIMEOUT = 2000; // ms
// Using weak keys and values ensures that we will not hold up garbage collection
protected BlockingHashMap<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>();
protected ConcurrentMap<InputStream, SocketAddress> inputLookup = new MapMaker().weakValues().makeMap();
// The timeout
private final long injectorTimeout;
public InputStreamReflectLookup(ErrorReporter reporter, Server server) {
this(reporter, server, DEFAULT_TIMEOUT);
}
/**
* Initialize a reflect lookup with a given default injector timeout.
* <p>
* This timeout defines the maximum amount of time to wait until an injector has been discovered.
* @param reporter - the error reporter.
* @param server - the current Bukkit server.
* @param injectorTimeout - the injector timeout.
*/
public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) {
super(reporter, server);
this.injectorTimeout = injectorTimeout;
}
@Override
public void inject(Object container) {
// Do nothing
}
@Override
public SocketInjector peekSocketInjector(SocketAddress address) {
try {
return addressLookup.get(address, 0, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
// Whatever
return null;
}
}
@Override
public SocketInjector waitSocketInjector(SocketAddress address) {
try {
// Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to
// periodically wake up waiting readers and writers. We have to wait for the dedicated server thread
// to catch up, so we'll swallow these interrupts.
//
// TODO: Consider if we should raise the thread priority of the dedicated server listener thread.
return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true);
} catch (InterruptedException e) {
// This cannot be!
throw new IllegalStateException("Impossible exception occured!", e);
}
}
@Override
public SocketInjector waitSocketInjector(Socket socket) {
return waitSocketInjector(socket.getRemoteSocketAddress());
}
@Override
public SocketInjector waitSocketInjector(InputStream input) {
try {
SocketAddress address = waitSocketAddress(input);
// Guard against NPE
if (address != null)
return waitSocketInjector(address);
else
return null;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot find or access socket field for " + input, e);
}
}
/**
* Use reflection to get the underlying socket address from an input stream.
* @param stream - the socket stream to lookup.
* @return The underlying socket address, or NULL if not found.
* @throws IllegalAccessException Unable to access socket field.
*/
private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException {
// Extra check, just in case
if (stream instanceof FilterInputStream)
return waitSocketAddress(getInputStream((FilterInputStream) stream));
SocketAddress result = inputLookup.get(stream);
if (result == null) {
Socket socket = lookupSocket(stream);
// Save it
result = socket.getRemoteSocketAddress();
inputLookup.put(stream, result);
}
return result;
}
/**
* Retrieve the underlying input stream that is associated with a given filter input stream.
* @param filtered - the filter input stream.
* @return The underlying input stream that is being filtered.
* @throws FieldAccessException Unable to access input stream.
*/
protected static InputStream getInputStream(FilterInputStream filtered) {
if (filteredInputField == null)
filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true).
getFieldByType("in", InputStream.class);
InputStream current = filtered;
try {
// Iterate until we find the real input stream
while (current instanceof FilterInputStream) {
current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
}
return current;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access filtered input field.", e);
}
}
@Override
public void setSocketInjector(SocketAddress address, SocketInjector injector) {
if (address == null)
throw new IllegalArgumentException("address cannot be NULL");
if (injector == null)
throw new IllegalArgumentException("injector cannot be NULL.");
SocketInjector previous = addressLookup.put(address, injector);
// Any previous temporary players will also be associated
if (previous != null) {
// Update the reference to any previous injector
onPreviousSocketOverwritten(previous, injector);
}
}
@Override
public void cleanupAll() {
// Do nothing
}
/**
* Lookup the underlying socket of a stream through reflection.
* @param stream - the socket stream.
* @return The underlying socket.
* @throws IllegalAccessException If reflection failed.
*/
private static Socket lookupSocket(InputStream stream) throws IllegalAccessException {
if (stream instanceof FilterInputStream) {
return lookupSocket(getInputStream((FilterInputStream) stream));
} else {
// Just do it
Field socketField = FuzzyReflection.fromObject(stream, true).
getFieldByType("socket", Socket.class);
return (Socket) FieldUtils.readField(socketField, stream, true);
}
}
}
@@ -1,43 +0,0 @@
package com.comphenix.protocol.injector.server;
import com.comphenix.protocol.events.NetworkMarker;
/**
* Represents a single send packet command.
* @author Kristian
*/
class QueuedSendPacket {
private final Object packet;
private final NetworkMarker marker;
private final boolean filtered;
public QueuedSendPacket(Object packet, NetworkMarker marker, boolean filtered) {
this.packet = packet;
this.marker = marker;
this.filtered = filtered;
}
/**
* Retrieve the network marker.
* @return Marker.
*/
public NetworkMarker getMarker() {
return marker;
}
/**
* Retrieve the underlying packet that will be sent.
* @return The underlying packet.
*/
public Object getPacket() {
return packet;
}
/**
* Determine if the packet should be intercepted by packet listeners.
* @return TRUE if it should, FALSE otherwise.
*/
public boolean isFiltered() {
return filtered;
}
}
@@ -1,71 +0,0 @@
package com.comphenix.protocol.injector.server;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.SocketAddress;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.NetworkMarker;
/**
* Represents an injector that only gives access to a player's socket.
*
* @author Kristian
*/
public interface SocketInjector {
/**
* Retrieve the associated socket of this player.
* @return The associated socket.
* @throws IllegalAccessException If we're unable to read the socket field.
*/
public abstract Socket getSocket() throws IllegalAccessException;
/**
* Retrieve the associated address of this player.
* @return The associated address.
* @throws IllegalAccessException If we're unable to read the socket field.
*/
public abstract SocketAddress getAddress() throws IllegalAccessException;
/**
* Attempt to disconnect the current client.
* @param message - the message to display.
* @throws InvocationTargetException If disconnection failed.
*/
public abstract void disconnect(String message) throws InvocationTargetException;
/**
* Send a packet to the client.
* @param packet - server packet to send.
* @param marker - the network marker.
* @param filtered - whether or not the packet will be filtered by our listeners.
* @throws InvocationTargetException If an error occured when sending the packet.
*/
public abstract void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered)
throws InvocationTargetException;
/**
* Retrieve the hooked player.
* @return The hooked player.
*/
public abstract Player getPlayer();
/**
* Retrieve the hooked player object OR the more up-to-date player instance.
* @return The hooked player, or a more up-to-date instance.
*/
public abstract Player getUpdatedPlayer();
/**
* Invoked when a delegated socket injector transfers the state of one injector to the next.
* @param delegate - the new injector.
*/
public abstract void transferState(SocketInjector delegate);
/**
* Set the real Bukkit player that we will use.
* @param updatedPlayer - the real Bukkit player.
*/
public abstract void setUpdatedPlayer(Player updatedPlayer);
}
@@ -1,200 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.server;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.ChatExtensions;
/**
* Create fake player instances that represents pre-authenticated clients.
*/
public class TemporaryPlayerFactory {
// Prevent too many class creations
private static CallbackFilter callbackFilter;
/**
* Retrieve the injector from a given player if it contains one.
* @param player - the player that may contain a reference to a player injector.
* @return The referenced player injector, or NULL if none can be found.
*/
public static SocketInjector getInjectorFromPlayer(Player player) {
if (player instanceof InjectorContainer) {
return ((InjectorContainer) player).getInjector();
}
return null;
}
/**
* Set the player injector, if possible.
* @param player - the player to update.
* @param injector - the injector to store.
*/
public static void setInjectorInPlayer(Player player, SocketInjector injector) {
((InjectorContainer) player).setInjector(injector);
}
/**
* Construct a temporary player that supports a subset of every player command.
* <p>
* Supported methods include:
* <ul>
* <li>getPlayer()</li>
* <li>getAddress()</li>
* <li>getServer()</li>
* <li>chat(String)</li>
* <li>sendMessage(String)</li>
* <li>sendMessage(String[])</li>
* <li>kickPlayer(String)</li>
* </ul>
* <p>
* Note that a temporary player has not yet been assigned a name, and thus cannot be
* uniquely identified. Use the address instead.
* @param server - the current server.
* @return A temporary player instance.
*/
public Player createTemporaryPlayer(final Server server) {
// Default implementation
Callback implementation = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName();
SocketInjector injector = ((InjectorContainer) obj).getInjector();
if (injector == null)
throw new IllegalStateException("Unable to find injector.");
// Use the socket to get the address
else if (methodName.equals("getPlayer"))
return injector.getUpdatedPlayer();
else if (methodName.equals("getAddress"))
return injector.getAddress();
else if (methodName.equals("getServer"))
return server;
// Handle send message methods
if (methodName.equals("chat") || methodName.equals("sendMessage")) {
try {
Object argument = args[0];
// Dynamic overloading
if (argument instanceof String) {
return sendMessage(injector, (String) argument);
} else if (argument instanceof String[]) {
for (String message : (String[]) argument) {
sendMessage(injector, message);
}
return null;
}
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
// Also, handle kicking
if (methodName.equals("kickPlayer")) {
injector.disconnect((String) args[0]);
return null;
}
// The fallback instance
Player updated = injector.getUpdatedPlayer();
if (updated != obj && updated != null) {
return proxy.invoke(updated, args);
}
// Methods that are supported in the fallback instance
if (methodName.equals("isOnline"))
return injector.getSocket() != null && injector.getSocket().isConnected();
else if (methodName.equals("getName"))
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
// Ignore all other methods
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for temporary players.");
}
};
// Shared callback filter
if (callbackFilter == null) {
callbackFilter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Do not override the object method or the superclass methods
if (method.getDeclaringClass().equals(Object.class) ||
method.getDeclaringClass().equals(InjectorContainer.class))
return 0;
else
return 1;
}
};
}
// CGLib is amazing
Enhancer ex = new Enhancer();
ex.setSuperclass(InjectorContainer.class);
ex.setInterfaces(new Class[] { Player.class });
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
ex.setCallbackFilter(callbackFilter);
return (Player) ex.create();
}
/**
* Construct a temporary player with the given associated socket injector.
* @param server - the parent server.
* @param injector - the referenced socket injector.
* @return The temporary player.
*/
public Player createTemporaryPlayer(Server server, SocketInjector injector) {
Player temporary = createTemporaryPlayer(server);
((InjectorContainer) temporary).setInjector(injector);
return temporary;
}
/**
* Send a message to the given client.
* @param injector - the injector representing the client.
* @param message - a message.
* @return Always NULL.
* @throws InvocationTargetException If the message couldn't be sent.
* @throws FieldAccessException If we were unable to construct the message packet.
*/
private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
for (PacketContainer packet : ChatExtensions.createChatPackets(message)) {
injector.sendServerPacket(packet.getHandle(), null, false);
}
return null;
}
}
@@ -1,54 +0,0 @@
package com.comphenix.protocol.injector.spigot;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.injector.packet.PacketInjector;
public abstract class AbstractPacketInjector implements PacketInjector {
private PacketTypeSet reveivedFilters;
public AbstractPacketInjector(PacketTypeSet reveivedFilters) {
this.reveivedFilters = reveivedFilters;
}
@Override
public boolean isCancelled(Object packet) {
// No, it's never cancelled
return false;
}
@Override
public void setCancelled(Object packet, boolean cancelled) {
throw new UnsupportedOperationException();
}
@Override
public boolean addPacketHandler(PacketType type, Set<ListenerOptions> options) {
reveivedFilters.addType(type);
return true;
}
@Override
public boolean removePacketHandler(PacketType type) {
reveivedFilters.removeType(type);
return true;
}
@Override
public boolean hasPacketHandler(PacketType type) {
return reveivedFilters.contains(type);
}
@Override
public Set<PacketType> getPacketHandlers() {
return reveivedFilters.values();
}
@Override
public void cleanupAll() {
reveivedFilters.clear();
}
}
@@ -1,83 +0,0 @@
package com.comphenix.protocol.injector.spigot;
import java.io.DataInputStream;
import java.util.Set;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
public abstract class AbstractPlayerHandler implements PlayerInjectionHandler {
protected PacketTypeSet sendingFilters;
public AbstractPlayerHandler(PacketTypeSet sendingFilters) {
this.sendingFilters = sendingFilters;
}
@Override
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
sendingFilters.addType(type);
}
@Override
public void removePacketHandler(PacketType type) {
sendingFilters.removeType(type);
}
@Override
public Set<PacketType> getSendingFilters() {
return sendingFilters.values();
}
@Override
public void close() {
sendingFilters.clear();
}
@Override
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
@Override
public boolean canRecievePackets() {
return true;
}
@Override
public PlayerInjectHooks getPlayerHook() {
// Pretend that we do
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
@Override
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public void checkListener(PacketListener listener) {
// They're all fine!
}
@Override
public void checkListener(Set<PacketListener> listeners) {
// Yes, really
}
}
@@ -1,45 +0,0 @@
package com.comphenix.protocol.injector.spigot;
import java.util.Set;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.Sets;
/**
* Dummy packet injector that simply delegates to its parent Spigot packet injector or receiving filters.
*
* @author Kristian
*/
class DummyPacketInjector extends AbstractPacketInjector {
private SpigotPacketInjector injector;
private PacketTypeSet lastBufferedPackets = new PacketTypeSet();
public DummyPacketInjector(SpigotPacketInjector injector, PacketTypeSet reveivedFilters) {
super(reveivedFilters);
this.injector = injector;
}
@Override
public void inputBuffersChanged(Set<PacketType> set) {
Set<PacketType> removed = Sets.difference(lastBufferedPackets.values(), set);
Set<PacketType> added = Sets.difference(set, lastBufferedPackets.values());
// Update the proxy packet injector
for (PacketType packet : removed) {
injector.getProxyPacketInjector().removePacketHandler(packet);
}
for (PacketType packet : added) {
injector.getProxyPacketInjector().addPacketHandler(packet, null);
}
}
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
return injector.packetReceived(packet, client, buffered);
}
}
@@ -1,91 +0,0 @@
package com.comphenix.protocol.injector.spigot;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.compat.netty.WrappedChannel;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
/**
* Dummy player handler that simply delegates to its parent Spigot packet injector.
*
* @author Kristian
*/
class DummyPlayerHandler extends AbstractPlayerHandler {
private SpigotPacketInjector injector;
@Override
public int getProtocolVersion(Player player) {
// Just use the server version
return MinecraftProtocolVersion.getCurrentVersion();
}
public DummyPlayerHandler(SpigotPacketInjector injector, PacketTypeSet sendingFilters) {
super(sendingFilters);
this.injector = injector;
}
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
return true;
}
@Override
public boolean uninjectPlayer(Player player) {
injector.uninjectPlayer(player);
return true;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
injector.sendServerPacket(receiver, packet, marker, filters);
}
@Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
injector.processPacket(player, mcPacket);
}
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
// We don't care about strategy
injector.injectPlayer(player);
}
@Override
public boolean hasMainThreadListener(PacketType type) {
return sendingFilters.contains(type);
}
@Override
public void handleDisconnect(Player player) {
// Just ignore
}
@Override
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
// Associate this buffered data
if (buffered != null) {
injector.saveBuffered(packet.getHandle(), buffered);
}
return null;
}
@Override
public void updatePlayer(Player player) {
// Do nothing
}
@Override
public WrappedChannel getChannel(Player player) {
throw new UnsupportedOperationException();
}
}
@@ -1,672 +0,0 @@
package com.comphenix.protocol.injector.spigot;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.DelegatedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.packet.LegacyNetworkMarker;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.player.NetworkObjectInjector;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
/**
* Offload all the work to Spigot, if possible.
*
* @author Kristian
*/
public class SpigotPacketInjector implements SpigotPacketListener {
public static final ReportType REPORT_CANNOT_CLEANUP_SPIGOT = new ReportType("Cannot cleanup Spigot listener.");
// Lazily retrieve the spigot listener class
private static volatile Class<?> spigotListenerClass;
private static volatile boolean classChecked;
// Retrieve the entity player from a PlayerConnection
private static volatile Field playerConnectionPlayer;
// Packets that are not to be processed by the filters
private Set<Object> ignoredPackets = Collections.newSetFromMap(new MapMaker().weakKeys().<Object, Boolean>makeMap());
/**
* The amount of ticks to wait before removing all traces of a player.
*/
private static final int CLEANUP_DELAY = 100;
// The listener we will register on Spigot.
// Unfortunately, due to the use of PlayerConnection, INetworkManager and Packet, we're
// unable to reference it directly. But with CGLib, it shouldn't cost us much.
private Object dynamicListener;
// Reference to ProtocolLib
private Plugin plugin;
// Different sending filters
private PacketTypeSet queuedFilters;
private PacketTypeSet reveivedFilters;
// NetworkManager to injector and player
private ConcurrentMap<Object, NetworkObjectInjector> networkManagerInjector = Maps.newConcurrentMap();
// Player to injector
private ConcurrentMap<Player, NetworkObjectInjector> playerInjector = Maps.newConcurrentMap();
// For handling read buffered packet data
private Map<Object, byte[]> readBufferedPackets = new MapMaker().weakKeys().makeMap();
// Responsible for informing the PL packet listeners
private ListenerInvoker invoker;
private ErrorReporter reporter;
private Server server;
// The proxy packet injector
private PacketInjector proxyPacketInjector;
// Background task
private static final int BACKGROUND_DELAY = 30 * PacketFilterManager.TICKS_PER_SECOND;
private int backgroundId;
/**
* Create a new spigot injector.
* @param reporter - error reporter
* @param invoker - listener invoker
* @param server - server
*/
public SpigotPacketInjector(ErrorReporter reporter, ListenerInvoker invoker, Server server) {
this.reporter = reporter;
this.invoker = invoker;
this.server = server;
this.queuedFilters = new PacketTypeSet();
this.reveivedFilters = new PacketTypeSet();
}
/**
* Retrieve the underlying listener invoker.
* @return The invoker.
*/
public ListenerInvoker getInvoker() {
return invoker;
}
/**
* Set the real proxy packet injector.
* @param proxyPacketInjector - the real injector.
*/
public void setProxyPacketInjector(PacketInjector proxyPacketInjector) {
this.proxyPacketInjector = proxyPacketInjector;
}
/**
* Retrieve the real proxy packet injector.
* @return The real injector.
*/
public PacketInjector getProxyPacketInjector() {
return proxyPacketInjector;
}
/**
* Retrieve the spigot packet listener class.
* @return The listener class.
*/
private static Class<?> getSpigotListenerClass() {
if (!classChecked) {
try {
spigotListenerClass = SpigotPacketInjector.class.getClassLoader().loadClass("org.spigotmc.netty.PacketListener");
} catch (ClassNotFoundException e) {
return null;
} finally {
// We've given it a try now
classChecked = true;
}
}
return spigotListenerClass;
}
/**
* Retrieve the register packet listener method.
* @return The method used to register a packet listener.
*/
private static Method getRegisterMethod() {
Class<?> clazz = getSpigotListenerClass();
if (clazz != null) {
try {
return clazz.getMethod("register", clazz, Plugin.class);
} catch (SecurityException e) {
// If this happens, then ... we're doomed
throw new RuntimeException("Reflection is not allowed.", e);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Cannot find register() method in " + clazz, e);
}
}
// Also bad
throw new IllegalStateException("Spigot could not be found!");
}
/**
* Determine if there is a Spigot packet listener.
* @return Spigot packet listener.
*/
public static boolean canUseSpigotListener() {
return getSpigotListenerClass() != null;
}
/**
* Register the Spigot packet injector.
* @param plugin - the parent plugin.
* @return TRUE if we registered the plugin, FALSE otherwise.
*/
public boolean register(Plugin plugin) {
if (hasRegistered())
return false;
// Save the plugin too
this.plugin = plugin;
final Callback[] callbacks = new Callback[3];
final boolean[] found = new boolean[3];
// Packets received from the clients
callbacks[0] = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return SpigotPacketInjector.this.packetReceived(args[0], args[1], args[2]);
}
};
// Packet sent/queued
callbacks[1] = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return SpigotPacketInjector.this.packetQueued(args[0], args[1], args[2]);
}
};
// Don't care for everything else
callbacks[2] = NoOp.INSTANCE;
Enhancer enhancer = EnhancerFactory.getInstance().createEnhancer();
enhancer.setSuperclass(getSpigotListenerClass());
enhancer.setCallbacks(callbacks);
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
// We'll be pretty stringent
if (matchMethod("packetReceived", method)) {
found[0] = true;
return 0;
} else if (matchMethod("packetQueued", method)) {
found[1] = true;
return 1;
} else {
found[2] = true;
return 2;
}
}
});
dynamicListener = enhancer.create();
// Verify methods
if (!found[0])
throw new IllegalStateException("Unable to find a valid packet receiver in Spigot.");
if (!found[1])
throw new IllegalStateException("Unable to find a valid packet queue in Spigot.");
// Lets register it too
try {
getRegisterMethod().invoke(null, dynamicListener, plugin);
} catch (Exception e) {
throw new RuntimeException("Cannot register Spigot packet listener.", e);
}
// Remember to register background task
backgroundId = createBackgroundTask();
// If we succeed
return true;
}
/**
* Create and register a background task.
* @return The background task ID.
*/
private int createBackgroundTask() {
return Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new Runnable() {
@Override
public void run() {
cleanupInjectors();
}
}, BACKGROUND_DELAY, BACKGROUND_DELAY);
}
/**
* Ensure that all disconnected injectors are removed from memory.
*/
private void cleanupInjectors() {
for (NetworkObjectInjector injector : networkManagerInjector.values()) {
try {
if (injector.getSocket() != null && injector.getSocket().isClosed()) {
cleanupInjector(injector);
}
} catch (Exception e) {
reporter.reportMinimal(plugin, "cleanupInjectors", e);
// What?
cleanupInjector(injector);
}
}
}
/**
* Remove a given network object injector.
* @param injector - the injector.
*/
private void cleanupInjector(final NetworkObjectInjector injector) {
// Clean up
playerInjector.remove(injector.getPlayer());
playerInjector.remove(injector.getUpdatedPlayer());
networkManagerInjector.remove(injector.getNetworkManager());
}
/**
* Determine if the given method is a valid packet receiver or queued method.
* @param methodName - the expected name of the method.
* @param method - the method we're testing.
* @return TRUE if this is a correct method, FALSE otherwise.
*/
private boolean matchMethod(String methodName, Method method) {
return FuzzyMethodContract.newBuilder().
nameExact(methodName).
parameterCount(3).
parameterSuperOf(MinecraftReflection.getNetHandlerClass(), 1).
parameterSuperOf(MinecraftReflection.getPacketClass(), 2).
returnTypeExact(MinecraftReflection.getPacketClass()).
build().
isMatch(MethodInfo.fromMethod(method), null);
}
/**
* Determine if the Spigot packet listener has been registered.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean hasRegistered() {
return dynamicListener != null;
}
/**
* Retrieve the dummy player injection handler.
* @return Dummy player injection handler.
*/
public PlayerInjectionHandler getPlayerHandler() {
return new DummyPlayerHandler(this, queuedFilters);
}
/**
* Retrieve the dummy packet injection handler.
* @return Dummy packet injection handler.
*/
public PacketInjector getPacketInjector() {
return new DummyPacketInjector(this, reveivedFilters);
}
/**
* Retrieve the currently registered injector for the given player.
* @param player - injected player.
* @param createNew - whether or not to create a new injector if the current is missing.
* @return The injector.
*/
NetworkObjectInjector getInjector(Player player, boolean createNew) {
NetworkObjectInjector injector = playerInjector.get(player);
if (injector == null && createNew) {
// Check for temporary players ..
if ((player instanceof Factory))
throw new IllegalArgumentException("Cannot inject tempoary player " + player);
try {
NetworkObjectInjector created = new NetworkObjectInjector(
filterImpossibleWarnings(reporter), null, invoker, null);
created.initializePlayer(player);
if (created.getNetworkManager() == null)
throw new PlayerLoggedOutException("Player " + player + " has logged out.");
injector = saveInjector(created.getNetworkManager(), created);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create dummy injector.", e);
}
}
return injector;
}
/**
* Retrieve or create a registered injector for the given network manager and connection.
* @param networkManager - a INetworkManager object.
* @param connection - a Connection (PlayerConnection, PendingConnection) object.
* @return The created NetworkObjectInjector with a temporary player.
*/
NetworkObjectInjector getInjector(Object networkManager, Object connection) {
NetworkObjectInjector dummyInjector = networkManagerInjector.get(networkManager);
if (dummyInjector == null) {
// Inject the network manager
try {
NetworkObjectInjector created = new NetworkObjectInjector(
filterImpossibleWarnings(reporter), null, invoker, null);
if (MinecraftReflection.isLoginHandler(connection)) {
created.initialize(connection);
created.setPlayer(created.createTemporaryPlayer(server));
} else if (MinecraftReflection.isServerHandler(connection)) {
// Get the player instead
if (playerConnectionPlayer == null)
playerConnectionPlayer = FuzzyReflection.fromObject(connection).
getFieldByType("player", MinecraftReflection.getEntityPlayerClass());
Object entityPlayer = playerConnectionPlayer.get(connection);
created.initialize(MinecraftReflection.getBukkitEntity(entityPlayer));
} else {
throw new IllegalArgumentException("Unregonized connection in NetworkManager.");
}
dummyInjector = saveInjector(networkManager, created);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create dummy injector.", e);
}
}
return dummyInjector;
}
/**
* Return a delegated error reporter that ignores certain warnings that are irrelevant on Spigot.
* @param reporter - error reporter to delegate.
* @return The filtered error reporter.
*/
private ErrorReporter filterImpossibleWarnings(ErrorReporter reporter) {
return new DelegatedErrorReporter(reporter) {
@Override
protected Report filterReport(Object sender, Report report, boolean detailed) {
// This doesn't matter - ignore it
if (report.getType() == NetworkObjectInjector.REPORT_DETECTED_CUSTOM_SERVER_HANDLER)
return null;
return report;
}
};
}
/**
* Save a given player injector for later.
* @param networkManager - the associated network manager.
* @param created - the created network object creator.
* @return Any other network injector that came before us.
*/
private NetworkObjectInjector saveInjector(Object networkManager, NetworkObjectInjector created) {
// Concurrency - use the same injector!
NetworkObjectInjector result = networkManagerInjector.putIfAbsent(networkManager, created);
if (result == null) {
result = created;
}
// Save the player as well
playerInjector.put(created.getPlayer(), created);
return result;
}
/**
* Save the buffered serialized input packet.
* @param handle - the associated packet.
* @param buffered - the buffere data to save.
*/
public void saveBuffered(Object handle, byte[] buffered) {
readBufferedPackets.put(handle, buffered);
}
@Override
public Object packetReceived(Object networkManager, Object connection, Object packet) {
if (reveivedFilters.contains(packet.getClass())) {
@SuppressWarnings("deprecation")
Integer id = invoker.getPacketID(packet);
// Check for ignored packets
if (ignoredPackets.remove(packet)) {
return packet;
}
Player sender = getInjector(networkManager, connection).getUpdatedPlayer();
PacketType type = PacketType.findLegacy(id, Sender.CLIENT);
PacketContainer container = new PacketContainer(type, packet);
PacketEvent event = packetReceived(container, sender, readBufferedPackets.get(packet));
if (!event.isCancelled())
return event.getPacket().getHandle();
else
return null; // Cancel
}
// Don't change anything
return packet;
}
@Override
public Object packetQueued(Object networkManager, Object connection, Object packet) {
if (queuedFilters.contains(packet.getClass())) {
@SuppressWarnings("deprecation")
Integer id = invoker.getPacketID(packet);
// Check for ignored packets
if (ignoredPackets.remove(packet)) {
return packet;
}
Player reciever = getInjector(networkManager, connection).getUpdatedPlayer();
PacketType type = PacketType.findLegacy(id, Sender.SERVER);
PacketContainer container = new PacketContainer(type, packet);
PacketEvent event = packetQueued(container, reciever);
if (!event.isCancelled())
return event.getPacket().getHandle();
else
return null; // Cancel
}
// Don't change anything
return packet;
}
/**
* Called to inform the event listeners of a queued packet.
* @param packet - the packet that is to be sent.
* @param receiver - the receiver of this packet.
* @return The packet event that was used.
*/
PacketEvent packetQueued(PacketContainer packet, Player receiver) {
PacketEvent event = PacketEvent.fromServer(this, packet, receiver);
invoker.invokePacketSending(event);
return event;
}
/**
* Called to inform the event listeners of a received packet.
* @param packet - the packet that has been receieved.
* @param sender - the client packet.
* @return The packet event that was used.
*/
PacketEvent packetReceived(PacketContainer packet, Player sender, byte[] buffered) {
NetworkMarker marker = buffered != null ? new LegacyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered, packet.getType()) : null;
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
invoker.invokePacketRecieving(event);
return event;
}
/**
* Called when a player has logged in properly.
* @param player - the player that has logged in.
*/
void injectPlayer(Player player) {
try {
NetworkObjectInjector dummy = new NetworkObjectInjector(
filterImpossibleWarnings(reporter), player, invoker, null);
dummy.initializePlayer(player);
// Save this player for the network manager
NetworkObjectInjector realInjector = networkManagerInjector.get(dummy.getNetworkManager());
if (realInjector != null) {
// Update all future references
realInjector.setUpdatedPlayer(player);
playerInjector.put(player, realInjector);
} else {
// Ah - in that case, save this injector
saveInjector(dummy.getNetworkManager(), dummy);
}
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot inject " + player);
}
}
/**
* Uninject the given player.
* @param player - the player to uninject.
*/
void uninjectPlayer(Player player) {
final NetworkObjectInjector injector = getInjector(player, false);
if (player != null && injector != null) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
@Override
public void run() {
cleanupInjector(injector);
}
}, CLEANUP_DELAY);
}
}
/**
* Invoked when a plugin wants to sent a packet.
* @param receiver - the packet receiver.
* @param packet - the packet to transmit.
* @param marker - the network marker object.
* @param filters - whether or not to invoke the packet listeners.
* @throws InvocationTargetException If anything went wrong.
*/
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
NetworkObjectInjector networkObject = getInjector(receiver, true);
// If TRUE, process this packet like any other
if (filters)
ignoredPackets.remove(packet.getHandle());
else
ignoredPackets.add(packet.getHandle());
networkObject.sendServerPacket(packet.getHandle(), marker, filters);
}
/**
* Invoked when a plugin wants to simulate receiving a packet.
* @param player - the supposed sender.
* @param mcPacket - the packet to receieve.
* @throws IllegalAccessException Reflection is not permitted.
* @throws InvocationTargetException Minecraft threw an exception.
*/
void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
NetworkObjectInjector networkObject = getInjector(player, true);
// We will always ignore this packet
ignoredPackets.add(mcPacket);
networkObject.processPacket(mcPacket);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void cleanupListener() {
Class<?> listenerClass = getSpigotListenerClass();
// Yeah ... there's no easy way to remove the listener
synchronized (listenerClass) {
try {
Field listenersField = FieldUtils.getField(listenerClass, "listeners", true);
Field bakedField = FieldUtils.getField(listenerClass, "baked", true);
Map<Object, Plugin> listenerMap = (Map<Object, Plugin>) listenersField.get(null);
List<Object> listenerArray = Lists.newArrayList((Object[]) bakedField.get(null));
listenerMap.remove(dynamicListener);
listenerArray.remove(dynamicListener);
// Save the array back
bakedField.set(null, Iterables.toArray(listenerArray, (Class)listenerClass));
// Success
dynamicListener = null;
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_CLEANUP_SPIGOT).
callerParam(dynamicListener).error(e));
}
}
}
/**
* Invoked when the server is cleaning up.
*/
public void cleanupAll() {
// Cleanup the Spigot listener
if (dynamicListener != null) {
cleanupListener();
}
if (backgroundId >= 0) {
Bukkit.getScheduler().cancelTask(backgroundId);
backgroundId = -1;
}
// Cleanup network marker
if (proxyPacketInjector != null) {
proxyPacketInjector.cleanupAll();
}
}
}
@@ -1,34 +0,0 @@
package com.comphenix.protocol.injector.spigot;
/**
* Represents a proxy for a Spigot packet listener.
*
* @author Kristian
*/
interface SpigotPacketListener {
/**
* Called when a packet has been received and is about to be handled by the
* current Connection.
* <p>
* The returned packet will be the packet passed on for handling, or in the case of
* null being returned, not handled at all.
*
* @param networkManager - the NetworkManager receiving the packet
* @param connection - the connection which will handle the packet
* @param packet - the received packet
* @return the packet to be handled, or null to cancel
*/
public Object packetReceived(Object networkManager, Object connection, Object packet);
/**
* Called when a packet is queued to be sent.
* <p>
* The returned packet will be the packet sent. In the case of null being returned,
* the packet will not be sent.
* @param networkManager - the NetworkManager which will send the packet
* @param connection - the connection which queued the packet
* @param packet - the queue packet
* @return the packet to be sent, or null if the packet will not be sent.
*/
public Object packetQueued(Object networkManager, Object connection, Object packet);
}
@@ -1,753 +0,0 @@
/*
* Copyright 2011-2013 Tyler Blair. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and contributors and should not be interpreted as representing official policies,
* either expressed or implied, of anybody else.
*/
package com.comphenix.protocol.metrics;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.zip.GZIPOutputStream;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import com.comphenix.protocol.utility.Util;
import com.comphenix.protocol.utility.WrappedScheduler;
public class Metrics {
/**
* The current revision number
*/
private final static int REVISION = 7;
/**
* The base url of the metrics domain
*/
private static final String BASE_URL = "http://report.mcstats.org";
/**
* The url used to report a server's status
*/
private static final String REPORT_URL = "/plugin/%s";
/**
* Interval of time to ping (in minutes)
*/
private static final int PING_INTERVAL = 15;
/**
* The plugin this metrics submits for
*/
private final Plugin plugin;
/**
* All of the custom graphs to submit to metrics
*/
private final Set<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
/**
* The plugin configuration file
*/
private final YamlConfiguration configuration;
/**
* The plugin configuration file
*/
private final File configurationFile;
/**
* Unique server id
*/
private final String guid;
/**
* Debug mode
*/
private final boolean debug;
/**
* Lock for synchronization
*/
private final Object optOutLock = new Object();
/**
* The scheduled task
*/
private volatile WrappedScheduler.TaskWrapper task = null;
public Metrics(final Plugin plugin) throws IOException {
if (plugin == null) {
throw new IllegalArgumentException("Plugin cannot be null");
}
this.plugin = plugin;
// load the config
configurationFile = getConfigFile();
configuration = YamlConfiguration.loadConfiguration(configurationFile);
// add some defaults
configuration.addDefault("opt-out", false);
configuration.addDefault("guid", UUID.randomUUID().toString());
configuration.addDefault("debug", false);
// Do we need to create the file?
if (configuration.get("guid", null) == null) {
configuration.options().header("http://mcstats.org").copyDefaults(true);
configuration.save(configurationFile);
}
// Load the guid then
guid = configuration.getString("guid");
debug = configuration.getBoolean("debug", false);
}
/**
* Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics
* website. Plotters can be added to the graph object returned.
*
* @param name The name of the graph
* @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given
*/
public Graph createGraph(final String name) {
if (name == null) {
throw new IllegalArgumentException("Graph name cannot be null");
}
// Construct the graph object
final Graph graph = new Graph(name);
// Now we can add our graph
graphs.add(graph);
// and return back
return graph;
}
/**
* Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend
*
* @param graph The name of the graph
*/
public void addGraph(final Graph graph) {
if (graph == null) {
throw new IllegalArgumentException("Graph cannot be null");
}
graphs.add(graph);
}
/**
* Start measuring statistics. This will immediately create an async repeating task as the plugin and send the
* initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200
* ticks.
*
* @return True if statistics measuring is running, otherwise false.
*/
public boolean start() {
synchronized (optOutLock) {
// Did we opt out?
if (isOptOut()) {
return false;
}
// Is metrics already running?
if (task != null) {
return true;
}
// Begin hitting the server with glorious data
task = WrappedScheduler.runAsynchronouslyRepeat(plugin, new Runnable() {
private boolean firstPost = true;
@Override
public void run() {
try {
// This has to be synchronized or it can collide with the disable method.
synchronized (optOutLock) {
// Disable Task, if it is running and the server owner decided to opt-out
if (isOptOut() && task != null) {
task.cancel();
task = null;
// Tell all plotters to stop gathering information.
for (Graph graph : graphs) {
graph.onOptOut();
}
}
}
// We use the inverse of firstPost because if it is the first time we are posting,
// it is not a interval ping, so it evaluates to FALSE
// Each time thereafter it will evaluate to TRUE, i.e PING!
postPlugin(!firstPost);
// After the first post we set firstPost to false
// Each post thereafter will be a ping
firstPost = false;
} catch (IOException e) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage());
}
}
}
}, 0, PING_INTERVAL * 1200);
return true;
}
}
/**
* Has the server owner denied plugin metrics?
*
* @return true if metrics should be opted out of it
*/
public boolean isOptOut() {
synchronized (optOutLock) {
try {
// Reload the metrics file
configuration.load(getConfigFile());
} catch (IOException ex) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
} catch (InvalidConfigurationException ex) {
if (debug) {
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
}
return true;
}
return configuration.getBoolean("opt-out", false);
}
}
/**
* Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task.
*
* @throws IOException If configuration saving fails
*/
public void enable() throws IOException {
// This has to be synchronized or it can collide with the check in the task.
synchronized (optOutLock) {
// Check if the server owner has already set opt-out, if not, set it.
if (isOptOut()) {
configuration.set("opt-out", false);
configuration.save(configurationFile);
}
// Enable Task, if it is not running
if (task == null) {
start();
}
}
}
/**
* Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task.
*
* @throws IOException If configuration saving fails
*/
public void disable() throws IOException {
// This has to be synchronized or it can collide with the check in the task.
synchronized (optOutLock) {
// Check if the server owner has already set opt-out, if not, set it.
if (!isOptOut()) {
configuration.set("opt-out", true);
configuration.save(configurationFile);
}
// Disable Task, if it is running
if (task != null) {
task.cancel();
task = null;
}
}
}
/**
* Gets the File object of the config file that should be used to store data such as the GUID and opt-out status
*
* @return the File object for the config file
*/
public File getConfigFile() {
// I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use
// is to abuse the plugin object we already have
// plugin.getDataFolder() => base/plugins/PluginA/
// pluginsFolder => base/plugins/
// The base is not necessarily relative to the startup directory.
File pluginsFolder = plugin.getDataFolder().getParentFile();
// return => base/plugins/PluginMetrics/config.yml
return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml");
}
/**
* Generic method that posts a plugin to the metrics website
*
* @throws IOException If posting fails
*/
private void postPlugin(final boolean isPing) throws IOException {
// Server software specific section
PluginDescriptionFile description = plugin.getDescription();
String pluginName = description.getName();
boolean onlineMode = Bukkit.getServer().getOnlineMode(); // TRUE if online mode is enabled
String pluginVersion = trimVersion(description.getVersion()); // ProtocolLib
String serverVersion = Bukkit.getVersion();
int playersOnline = Util.getOnlinePlayers().size(); // ProtocolLib
// END server software specific section -- all code below does not use any code outside of this class / Java
// Construct the post data
StringBuilder json = new StringBuilder(1024);
json.append('{');
// The plugin's description file containg all of the plugin data such as name, version, author, etc
appendJSONPair(json, "guid", guid);
appendJSONPair(json, "plugin_version", pluginVersion);
appendJSONPair(json, "server_version", serverVersion);
appendJSONPair(json, "players_online", Integer.toString(playersOnline));
// New data as of R6
String osname = System.getProperty("os.name");
String osarch = System.getProperty("os.arch");
String osversion = System.getProperty("os.version");
String java_version = System.getProperty("java.version");
int coreCount = Runtime.getRuntime().availableProcessors();
// normalize os arch .. amd64 -> x86_64
if (osarch.equals("amd64")) {
osarch = "x86_64";
}
appendJSONPair(json, "osname", osname);
appendJSONPair(json, "osarch", osarch);
appendJSONPair(json, "osversion", osversion);
appendJSONPair(json, "cores", Integer.toString(coreCount));
appendJSONPair(json, "auth_mode", onlineMode ? "1" : "0");
appendJSONPair(json, "java_version", java_version);
// If we're pinging, append it
if (isPing) {
appendJSONPair(json, "ping", "1");
}
if (graphs.size() > 0) {
synchronized (graphs) {
json.append(',');
json.append('"');
json.append("graphs");
json.append('"');
json.append(':');
json.append('{');
boolean firstGraph = true;
final Iterator<Graph> iter = graphs.iterator();
while (iter.hasNext()) {
Graph graph = iter.next();
StringBuilder graphJson = new StringBuilder();
graphJson.append('{');
for (Plotter plotter : graph.getPlotters()) {
appendJSONPair(graphJson, plotter.getColumnName(), Integer.toString(plotter.getValue()));
}
graphJson.append('}');
if (!firstGraph) {
json.append(',');
}
json.append(escapeJSON(graph.getName()));
json.append(':');
json.append(graphJson);
firstGraph = false;
}
json.append('}');
}
}
// close json
json.append('}');
// Create the url
URL url = new URL(BASE_URL + String.format(REPORT_URL, urlEncode(pluginName)));
// Connect to the website
URLConnection connection;
// Mineshafter creates a socks proxy, so we can safely bypass it
// It does not reroute POST requests so we need to go around it
if (isMineshafterPresent()) {
connection = url.openConnection(Proxy.NO_PROXY);
} else {
connection = url.openConnection();
}
byte[] uncompressed = json.toString().getBytes();
byte[] compressed = gzip(json.toString());
// Headers
connection.addRequestProperty("User-Agent", "MCStats/" + REVISION);
connection.addRequestProperty("Content-Type", "application/json");
connection.addRequestProperty("Content-Encoding", "gzip");
connection.addRequestProperty("Content-Length", Integer.toString(compressed.length));
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.setDoOutput(true);
if (debug) {
System.out.println("[Metrics] Prepared request for " + pluginName + " uncompressed=" + uncompressed.length + " compressed=" + compressed.length);
}
// Write the data
OutputStream os = connection.getOutputStream();
os.write(compressed);
os.flush();
// Now read the response
final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String response = reader.readLine();
// close resources
os.close();
reader.close();
if (response == null || response.startsWith("ERR") || response.startsWith("7")) {
if (response == null) {
response = "null";
} else if (response.startsWith("7")) {
response = response.substring(response.startsWith("7,") ? 2 : 1);
}
throw new IOException(response);
} else {
// Is this the first update this hour?
if (response.equals("1") || response.contains("This is your first update this hour")) {
synchronized (graphs) {
final Iterator<Graph> iter = graphs.iterator();
while (iter.hasNext()) {
final Graph graph = iter.next();
for (Plotter plotter : graph.getPlotters()) {
plotter.reset();
}
}
}
}
}
}
// Trim the version string to not include the build number
// Not doing this has an interesting effect on the version graph
private String trimVersion(String version) {
return version.contains("-b") ? version.substring(0, version.indexOf("-b")) : version;
}
/**
* GZip compress a string of bytes
*
* @param input Input to compress
* @return Compressed string
*/
public static byte[] gzip(String input) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = null;
try {
gzos = new GZIPOutputStream(baos);
gzos.write(input.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (gzos != null) try {
gzos.close();
} catch (IOException ignore) {
}
}
return baos.toByteArray();
}
/**
* Check if mineshafter is present. If it is, we need to bypass it to send POST requests
*
* @return true if mineshafter is installed on the server
*/
private boolean isMineshafterPresent() {
try {
Class.forName("mineshafter.MineServer");
return true;
} catch (Exception e) {
return false;
}
}
// Appends a json encoded key/value pair to the given string builder.
private static void appendJSONPair(StringBuilder json, String key, String value) {
boolean isValueNumeric = false;
try {
if (value.equals("0") || !value.endsWith("0")) {
Double.parseDouble(value);
isValueNumeric = true;
}
} catch (NumberFormatException e) {
isValueNumeric = false;
}
if (json.charAt(json.length() - 1) != '{') {
json.append(',');
}
json.append(escapeJSON(key));
json.append(':');
if (isValueNumeric) {
json.append(value);
} else {
json.append(escapeJSON(value));
}
}
// Escape a string to create a valid JSON string
private static String escapeJSON(String text) {
StringBuilder builder = new StringBuilder();
builder.append('"');
for (int index = 0; index < text.length(); index++) {
char chr = text.charAt(index);
switch (chr) {
case '"':
case '\\':
builder.append('\\');
builder.append(chr);
break;
case '\b':
builder.append("\\b");
break;
case '\t':
builder.append("\\t");
break;
case '\n':
builder.append("\\n");
break;
case '\r':
builder.append("\\r");
break;
default:
if (chr < ' ') {
String t = "000" + Integer.toHexString(chr);
builder.append("\\u" + t.substring(t.length() - 4));
} else {
builder.append(chr);
}
break;
}
}
builder.append('"');
return builder.toString();
}
// Encode text as UTF-8
private static String urlEncode(final String text) throws UnsupportedEncodingException {
return URLEncoder.encode(text, "UTF-8");
}
/**
* Represents a custom graph on the website
*/
public static class Graph {
/**
* The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is
* rejected
*/
private final String name;
/**
* The set of plotters that are contained within this graph
*/
private final Set<Plotter> plotters = new LinkedHashSet<Plotter>();
private Graph(final String name) {
this.name = name;
}
/**
* Gets the graph's name
*
* @return the Graph's name
*/
public String getName() {
return name;
}
/**
* Add a plotter to the graph, which will be used to plot entries
*
* @param plotter the plotter to add to the graph
*/
public void addPlotter(final Plotter plotter) {
plotters.add(plotter);
}
/**
* Remove a plotter from the graph
*
* @param plotter the plotter to remove from the graph
*/
public void removePlotter(final Plotter plotter) {
plotters.remove(plotter);
}
/**
* Gets an <b>unmodifiable</b> set of the plotter objects in the graph
*
* @return an unmodifiable {@link java.util.Set} of the plotter objects
*/
public Set<Plotter> getPlotters() {
return Collections.unmodifiableSet(plotters);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(final Object object) {
if (!(object instanceof Graph)) {
return false;
}
final Graph graph = (Graph) object;
return graph.name.equals(name);
}
/**
* Called when the server owner decides to opt-out of BukkitMetrics while the server is running.
*/
protected void onOptOut() {
}
}
/**
* Interface used to collect custom data for a plugin
*/
public static abstract class Plotter {
/**
* The plot's name
*/
private final String name;
/**
* Construct a plotter with the default plot name
*/
public Plotter() {
this("Default");
}
/**
* Construct a plotter with a specific plot name
*
* @param name the name of the plotter to use, which will show up on the website
*/
public Plotter(final String name) {
this.name = name;
}
/**
* Get the current value for the plotted point. Since this function defers to an external function it may or may
* not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called
* from any thread so care should be taken when accessing resources that need to be synchronized.
*
* @return the current value for the point to be plotted.
*/
public abstract int getValue();
/**
* Get the column name for the plotted point
*
* @return the plotted point's column name
*/
public String getColumnName() {
return name;
}
/**
* Called after the website graphs have been updated
*/
public void reset() {
}
@Override
public int hashCode() {
return getColumnName().hashCode();
}
@Override
public boolean equals(final Object object) {
if (!(object instanceof Plotter)) {
return false;
}
final Plotter plotter = (Plotter) object;
return plotter.name.equals(name) && plotter.getValue() == getValue();
}
}
}
@@ -1,82 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.metrics;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.metrics.Metrics.Graph;
public class Statistics {
// Metrics
private Metrics metrics;
public Statistics(Plugin plugin) throws IOException {
metrics = new Metrics(plugin);
// Determine who is using this library
addPluginUserGraph(metrics);
metrics.start();
}
private void addPluginUserGraph(Metrics metrics) {
Graph pluginUsers = metrics.createGraph("Plugin Users");
for (Map.Entry<String, Integer> entry : getPluginUsers(ProtocolLibrary.getProtocolManager()).entrySet()) {
final int count = entry.getValue();
// Plot plugins of this type
pluginUsers.addPlotter(new Metrics.Plotter(entry.getKey()) {
@Override
public int getValue() {
return count;
}
});
}
}
// Retrieve loaded plugins
private Map<String, Integer> getPluginUsers(ProtocolManager manager) {
Map<String, Integer> users = new HashMap<String, Integer>();
for (PacketListener listener : manager.getPacketListeners()) {
String name = PacketAdapter.getPluginName(listener);
// Increment occurence
if (!users.containsKey(name)) {
users.put(name, 1);
} else {
users.put(name, users.get(name) + 1);
}
}
return users;
}
}
@@ -1,138 +0,0 @@
package com.comphenix.protocol.reflect;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
import net.sf.cglib.asm.ClassReader;
import net.sf.cglib.asm.MethodVisitor;
import net.sf.cglib.asm.Opcodes;
import net.sf.cglib.asm.Type;
import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod.AsmOpcodes;
import com.comphenix.protocol.reflect.compiler.EmptyClassVisitor;
import com.comphenix.protocol.reflect.compiler.EmptyMethodVisitor;
import com.google.common.collect.Lists;
public class ClassAnalyser {
/**
* Represents a method in ASM.
* <p>
* Keep in mind that this may also invoke a constructor.
* @author Kristian
*/
public static class AsmMethod {
public enum AsmOpcodes {
INVOKE_VIRTUAL,
INVOKE_SPECIAL,
INVOKE_STATIC,
INVOKE_INTERFACE,
INVOKE_DYNAMIC;
public static AsmOpcodes fromIntOpcode(int opcode) {
switch (opcode) {
case Opcodes.INVOKEVIRTUAL: return AsmOpcodes.INVOKE_VIRTUAL;
case Opcodes.INVOKESPECIAL: return AsmOpcodes.INVOKE_SPECIAL;
case Opcodes.INVOKESTATIC: return AsmOpcodes.INVOKE_STATIC;
case Opcodes.INVOKEINTERFACE: return AsmOpcodes.INVOKE_INTERFACE;
case Opcodes.INVOKEDYNAMIC: return AsmOpcodes.INVOKE_DYNAMIC;
default: throw new IllegalArgumentException("Unknown opcode: " + opcode);
}
}
}
private final AsmOpcodes opcode;
private final String ownerClass;
private final String methodName;
private final String signature;
public AsmMethod(AsmOpcodes opcode, String ownerClass, String methodName, String signature) {
this.opcode = opcode;
this.ownerClass = ownerClass;
this.methodName = methodName;
this.signature = signature;
}
public String getOwnerName() {
return ownerClass;
}
/**
* Retrieve the opcode used to invoke this method or constructor.
* @return The opcode.
*/
public AsmOpcodes getOpcode() {
return opcode;
}
/**
* Retrieve the associated owner class.
* @return The owner class.
* @throws ClassNotFoundException If the class was not found
*/
public Class<?> getOwnerClass() throws ClassNotFoundException {
return AsmMethod.class.getClassLoader().loadClass(getOwnerName().replace('/', '.'));
}
public String getMethodName() {
return methodName;
}
public String getSignature() {
return signature;
}
}
private static final ClassAnalyser DEFAULT = new ClassAnalyser();
/**
* Retrieve the default instance.
* @return The default.
*/
public static ClassAnalyser getDefault() {
return DEFAULT;
}
/**
* Retrieve every method calls in the given method.
* @param method - the method to analyse.
* @return The method calls.
* @throws IOException Cannot access the parent class.
*/
public List<AsmMethod> getMethodCalls(Method method) throws IOException {
return getMethodCalls(method.getDeclaringClass(), method);
}
/**
* Retrieve every method calls in the given method.
* @param clazz - the parent class.
* @param method - the method to analyse.
* @return The method calls.
* @throws IOException Cannot access the parent class.
*/
public List<AsmMethod> getMethodCalls(Class<?> clazz, Method method) throws IOException {
final ClassReader reader = new ClassReader(clazz.getCanonicalName());
final List<AsmMethod> output = Lists.newArrayList();
// The method we are looking for
final String methodName = method.getName();
final String methodDescription = Type.getMethodDescriptor(method);
reader.accept(new EmptyClassVisitor() {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
// Check method
if (methodName.equals(name) && methodDescription.equals(desc)) {
return new EmptyMethodVisitor() {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
output.add(new AsmMethod(AsmOpcodes.fromIntOpcode(opcode), owner, methodName, desc));
}
};
}
return null;
}
}, ClassReader.EXPAND_FRAMES);
return output;
}
}
@@ -1,143 +0,0 @@
package com.comphenix.protocol.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import javax.annotation.Nonnull;
import com.google.common.base.Preconditions;
public class ExactReflection {
// The class we're actually representing
private Class<?> source;
private boolean forceAccess;
private ExactReflection(Class<?> source, boolean forceAccess) {
this.source = Preconditions.checkNotNull(source, "source class cannot be NULL");
this.forceAccess = forceAccess;
}
/**
* Retrieves an exact reflection instance from a given class.
* @param source - the class we'll use.
* @return A fuzzy reflection instance.
*/
public static ExactReflection fromClass(Class<?> source) {
return fromClass(source, false);
}
/**
* Retrieves an exact reflection instance from a given class.
* @param source - the class we'll use.
* @param forceAccess - whether or not to override scope restrictions.
* @return A fuzzy reflection instance.
*/
public static ExactReflection fromClass(Class<?> source, boolean forceAccess) {
return new ExactReflection(source, forceAccess);
}
/**
* Retrieves an exact reflection instance from an object.
* @param reference - the object we'll use.
* @return A fuzzy reflection instance that uses the class of the given object.
*/
public static ExactReflection fromObject(Object reference) {
return new ExactReflection(reference.getClass(), false);
}
/**
* Retrieves an exact reflection instance from an object.
* @param reference - the object we'll use.
* @param forceAccess - whether or not to override scope restrictions.
* @return A fuzzy reflection instance that uses the class of the given object.
*/
public static ExactReflection fromObject(Object reference, boolean forceAccess) {
return new ExactReflection(reference.getClass(), forceAccess);
}
/**
* Retrieve the first method in the class hierachy with the given name and parameters.
* <p>
* If {@link #isForceAccess()} is TRUE, we will also search for protected and private methods.
* @param methodName - the method name to find, or NULL to look for everything.
* @param parameters - the parameters.
* @return The first matched method.
* @throws IllegalArgumentException If we cannot find a method by this name.
*/
public Method getMethod(String methodName, Class<?>... parameters) {
return getMethod(source, methodName, parameters);
}
// For recursion
private Method getMethod(Class<?> instanceClass, String methodName, Class<?>... parameters) {
for (Method method : instanceClass.getDeclaredMethods()) {
if ((forceAccess || Modifier.isPublic(method.getModifiers())) &&
(methodName == null || method.getName().equals(methodName)) &&
Arrays.equals(method.getParameterTypes(), parameters)) {
method.setAccessible(true);
return method;
}
}
// Search in every superclass
if (instanceClass.getSuperclass() != null)
return getMethod(instanceClass.getSuperclass(), methodName, parameters);
throw new IllegalArgumentException(String.format(
"Unable to find method %s (%s) in %s.", methodName, Arrays.asList(parameters), source));
}
/**
* Retrieve a field in the class hierachy by the given name.
* <p>
* If {@link #isForceAccess()} is TRUE, we will also search for protected and private fields.
* @param fieldName - the field name. Cannot be NULL.
* @return The first matched field.
*/
public Field getField(String fieldName) {
return getField(source, fieldName);
}
// For recursion
private Field getField(Class<?> instanceClass, @Nonnull String fieldName) {
// Ignore access rules
for (Field field : instanceClass.getDeclaredFields()) {
if (field.getName().equals(fieldName)) {
field.setAccessible(true);
return field;
}
}
// Recursively fild the correct field
if (instanceClass.getSuperclass() != null)
return getField(instanceClass.getSuperclass(), fieldName);
throw new IllegalArgumentException(String.format(
"Unable to find field %s in %s.", fieldName, source));
}
/**
* Retrieve an {@link ExactReflection} object where scope restrictions are ignored.
* @return A copy of the current object.
*/
public ExactReflection forceAccess() {
return new ExactReflection(source, true);
}
/**
* Determine if we are overriding scope restrictions and will also find
* private, protected or package members.
* @return TRUE if we are, FALSE otherwise.
*/
public boolean isForceAccess() {
return forceAccess;
}
/**
* Retrieve the source class we are searching.
* @return The source.
*/
public Class<?> getSource() {
return source;
}
}
@@ -1,527 +0,0 @@
package com.comphenix.protocol.reflect;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Utilities for working with fields by reflection. Adapted and refactored from
* the dormant [reflect] Commons sandbox component.
* <p>
* The ability is provided to break the scoping restrictions coded by the
* programmer. This can allow fields to be changed that shouldn't be. This
* facility should be used with care.
*
* @author Apache Software Foundation
* @author Matt Benson
* @since 2.5
* @version $Id: FieldUtils.java 1057009 2011-01-09 19:48:06Z niallp $
*/
@SuppressWarnings("rawtypes")
public class FieldUtils {
/**
* FieldUtils instances should NOT be constructed in standard programming.
* <p>
* This constructor is public to permit tools that require a JavaBean
* instance to operate.
*/
public FieldUtils() {
super();
}
/**
* Gets an accessible <code>Field</code> by name respecting scope.
* Superclasses/interfaces will be considered.
*
* @param cls the class to reflect, must not be null
* @param fieldName the field name to obtain
* @return the Field object
* @throws IllegalArgumentException if the class or field name is null
*/
public static Field getField(Class cls, String fieldName) {
Field field = getField(cls, fieldName, false);
MemberUtils.setAccessibleWorkaround(field);
return field;
}
/**
* Gets an accessible <code>Field</code> by name breaking scope if
* requested. Superclasses/interfaces will be considered.
*
* @param cls the class to reflect, must not be null
* @param fieldName the field name to obtain
* @param forceAccess whether to break scope restrictions using the
* <code>setAccessible</code> method. <code>False</code> will
* only match public fields.
* @return the Field object
* @throws IllegalArgumentException if the class or field name is null
*/
public static Field getField(final Class cls, String fieldName, boolean forceAccess) {
if (cls == null) {
throw new IllegalArgumentException("The class must not be null");
}
if (fieldName == null) {
throw new IllegalArgumentException("The field name must not be null");
}
// Sun Java 1.3 has a bugged implementation of getField hence we write
// the
// code ourselves
// getField() will return the Field object with the declaring class
// set correctly to the class that declares the field. Thus requesting
// the
// field on a subclass will return the field from the superclass.
//
// priority order for lookup:
// searchclass private/protected/package/public
// superclass protected/package/public
// private/different package blocks access to further superclasses
// implementedinterface public
// check up the superclass hierarchy
for (Class acls = cls; acls != null; acls = acls.getSuperclass()) {
try {
Field field = acls.getDeclaredField(fieldName);
// getDeclaredField checks for non-public scopes as well
// and it returns accurate results
if (!Modifier.isPublic(field.getModifiers())) {
if (forceAccess) {
field.setAccessible(true);
} else {
continue;
}
}
return field;
} catch (NoSuchFieldException ex) {
// ignore
}
}
// check the public interface case. This must be manually searched for
// in case there is a public supersuperclass field hidden by a
// private/package
// superclass field.
Field match = null;
for (Iterator intf = getAllInterfaces(cls).iterator(); intf.hasNext();) {
try {
Field test = ((Class) intf.next()).getField(fieldName);
if (match != null) {
throw new IllegalArgumentException("Reference to field " + fieldName
+ " is ambiguous relative to " + cls
+ "; a matching field exists on two or more implemented interfaces.");
}
match = test;
} catch (NoSuchFieldException ex) {
// ignore
}
}
return match;
}
/**
* <p>Gets a <code>List</code> of all interfaces implemented by the given
* class and its superclasses.</p>
*
* <p>The order is determined by looking through each interface in turn as
* declared in the source file and following its hierarchy up. Then each
* superclass is considered in the same way. Later duplicates are ignored,
* so the order is maintained.</p>
*
* @param cls the class to look up, may be <code>null</code>
* @return the <code>List</code> of interfaces in order,
* <code>null</code> if null input
*/
private static List getAllInterfaces(Class cls) {
if (cls == null) {
return null;
}
List<Class> list = new ArrayList<Class>();
while (cls != null) {
Class[] interfaces = cls.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (list.contains(interfaces[i]) == false) {
list.add(interfaces[i]);
}
List superInterfaces = getAllInterfaces(interfaces[i]);
for (Iterator it = superInterfaces.iterator(); it.hasNext();) {
Class intface = (Class) it.next();
if (list.contains(intface) == false) {
list.add(intface);
}
}
}
cls = cls.getSuperclass();
}
return list;
}
/**
* Read an accessible static Field.
*
* @param field to read
* @return the field value
* @throws IllegalArgumentException if the field is null or not static
* @throws IllegalAccessException if the field is not accessible
*/
public static Object readStaticField(Field field) throws IllegalAccessException {
return readStaticField(field, false);
}
/**
* Read a static Field.
*
* @param field to read
* @param forceAccess whether to break scope restrictions using the
* <code>setAccessible</code> method.
* @return the field value
* @throws IllegalArgumentException if the field is null or not static
* @throws IllegalAccessException if the field is not made accessible
*/
public static Object readStaticField(Field field, boolean forceAccess)
throws IllegalAccessException {
if (field == null) {
throw new IllegalArgumentException("The field must not be null");
}
if (!Modifier.isStatic(field.getModifiers())) {
throw new IllegalArgumentException("The field '" + field.getName() + "' is not static");
}
return readField(field, (Object) null, forceAccess);
}
/**
* Read the named public static field. Superclasses will be considered.
*
* @param cls the class to reflect, must not be null
* @param fieldName the field name to obtain
* @return the value of the field
* @throws IllegalArgumentException if the class or field name is null
* @throws IllegalAccessException if the field is not accessible
*/
public static Object readStaticField(Class cls, String fieldName) throws IllegalAccessException {
return readStaticField(cls, fieldName, false);
}
/**
* Read the named static field. Superclasses will be considered.
*
* @param cls the class to reflect, must not be null
* @param fieldName the field name to obtain
* @param forceAccess whether to break scope restrictions using the
* <code>setAccessible</code> method. <code>False</code> will
* only match public fields.
* @return the Field object
* @throws IllegalArgumentException if the class or field name is null
* @throws IllegalAccessException if the field is not made accessible
*/
public static Object readStaticField(Class cls, String fieldName, boolean forceAccess)
throws IllegalAccessException {
Field field = getField(cls, fieldName, forceAccess);
if (field == null) {
throw new IllegalArgumentException("Cannot locate field " + fieldName + " on " + cls);
}
// already forced access above, don't repeat it here:
return readStaticField(field, false);
}
/**
* Read an accessible Field.
*
* @param field the field to use
* @param target the object to call on, may be null for static fields
* @return the field value
* @throws IllegalArgumentException if the field is null
* @throws IllegalAccessException if the field is not accessible
*/
public static Object readField(Field field, Object target) throws IllegalAccessException {
return readField(field, target, false);
}
/**
* Read a Field.
*
* @param field the field to use
* @param target the object to call on, may be null for static fields
* @param forceAccess whether to break scope restrictions using the
* <code>setAccessible</code> method.
* @return the field value
* @throws IllegalArgumentException if the field is null
* @throws IllegalAccessException if the field is not made accessible
*/
public static Object readField(Field field, Object target, boolean forceAccess) throws IllegalAccessException {
if (field == null)
throw new IllegalArgumentException("The field must not be null");
if (forceAccess && !field.isAccessible()) {
field.setAccessible(true);
} else {
MemberUtils.setAccessibleWorkaround(field);
}
return field.get(target);
}
/**
* Read the named public field. Superclasses will be considered.
*
* @param target the object to reflect, must not be null
* @param fieldName the field name to obtain
* @return the value of the field
* @throws IllegalArgumentException if the class or field name is null
* @throws IllegalAccessException if the named field is not public
*/
public static Object readField(Object target, String fieldName) throws IllegalAccessException {
return readField(target, fieldName, false);
}
/**
* Read the named field. Superclasses will be considered.
*
* @param target the object to reflect, must not be null
* @param fieldName the field name to obtain
* @param forceAccess whether to break scope restrictions using the
* <code>setAccessible</code> method. <code>False</code> will
* only match public fields.
* @return the field value
* @throws IllegalArgumentException if the class or field name is null
* @throws IllegalAccessException if the named field is not made accessible
*/
public static Object readField(Object target, String fieldName, boolean forceAccess)
throws IllegalAccessException {
if (target == null) {
throw new IllegalArgumentException("target object must not be null");
}
Class cls = target.getClass();
Field field = getField(cls, fieldName, forceAccess);
if (field == null) {
throw new IllegalArgumentException("Cannot locate field " + fieldName + " on " + cls);
}
// already forced access above, don't repeat it here:
return readField(field, target);
}
/**
* Write a public static Field.
*
* @param field to write
* @param value to set
* @throws IllegalArgumentException if the field is null or not static
* @throws IllegalAccessException if the field is not public or is final
*/
public static void writeStaticField(Field field, Object value) throws IllegalAccessException {
writeStaticField(field, value, false);
}
/**
* Write a static Field.
*
* @param field to write
* @param value to set
* @param forceAccess whether to break scope restrictions using the
* <code>setAccessible</code> method. <code>False</code> will
* only match public fields.
* @throws IllegalArgumentException if the field is null or not static
* @throws IllegalAccessException if the field is not made accessible or is
* final
*/
public static void writeStaticField(Field field, Object value, boolean forceAccess)
throws IllegalAccessException {
if (field == null) {
throw new IllegalArgumentException("The field must not be null");
}
if (!Modifier.isStatic(field.getModifiers())) {
throw new IllegalArgumentException("The field '" + field.getName() + "' is not static");
}
writeField(field, (Object) null, value, forceAccess);
}
/**
* Write a named public static Field. Superclasses will be considered.
*
* @param cls Class on which the Field is to be found
* @param fieldName to write
* @param value to set
* @throws IllegalArgumentException if the field cannot be located or is not
* static
* @throws IllegalAccessException if the field is not public or is final
*/
public static void writeStaticField(Class cls, String fieldName, Object value)
throws IllegalAccessException {
writeStaticField(cls, fieldName, value, false);
}
/**
* Write a named static Field. Superclasses will be considered.
*
* @param cls Class on which the Field is to be found
* @param fieldName to write
* @param value to set
* @param forceAccess whether to break scope restrictions using the
* <code>setAccessible</code> method. <code>False</code> will
* only match public fields.
* @throws IllegalArgumentException if the field cannot be located or is not
* static
* @throws IllegalAccessException if the field is not made accessible or is
* final
*/
public static void writeStaticField(Class cls, String fieldName, Object value,
boolean forceAccess) throws IllegalAccessException {
Field field = getField(cls, fieldName, forceAccess);
if (field == null) {
throw new IllegalArgumentException("Cannot locate field " + fieldName + " on " + cls);
}
// already forced access above, don't repeat it here:
writeStaticField(field, value);
}
public static void writeStaticFinalField(Class<?> clazz, String fieldName, Object value, boolean forceAccess) throws Exception {
Field field = getField(clazz, fieldName, forceAccess);
if (field == null) {
throw new IllegalArgumentException("Cannot locate field " + fieldName + " in " + clazz);
}
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.setAccessible(true);
field.set(null, value);
}
/**
* Write an accessible field.
*
* @param field to write
* @param target the object to call on, may be null for static fields
* @param value to set
* @throws IllegalArgumentException if the field is null
* @throws IllegalAccessException if the field is not accessible or is final
*/
public static void writeField(Field field, Object target, Object value)
throws IllegalAccessException {
writeField(field, target, value, false);
}
/**
* Write a field.
*
* @param field to write
* @param target the object to call on, may be null for static fields
* @param value to set
* @param forceAccess whether to break scope restrictions using the
* <code>setAccessible</code> method. <code>False</code> will
* only match public fields.
* @throws IllegalArgumentException if the field is null
* @throws IllegalAccessException if the field is not made accessible or is
* final
*/
public static void writeField(Field field, Object target, Object value, boolean forceAccess)
throws IllegalAccessException {
if (field == null) {
throw new IllegalArgumentException("The field must not be null");
}
if (forceAccess && !field.isAccessible()) {
field.setAccessible(true);
} else {
MemberUtils.setAccessibleWorkaround(field);
}
field.set(target, value);
}
/**
* Write a public field. Superclasses will be considered.
*
* @param target the object to reflect, must not be null
* @param fieldName the field name to obtain
* @param value to set
* @throws IllegalArgumentException if <code>target</code> or
* <code>fieldName</code> is null
* @throws IllegalAccessException if the field is not accessible
*/
public static void writeField(Object target, String fieldName, Object value)
throws IllegalAccessException {
writeField(target, fieldName, value, false);
}
/**
* Write a field. Superclasses will be considered.
*
* @param target the object to reflect, must not be null
* @param fieldName the field name to obtain
* @param value to set
* @param forceAccess whether to break scope restrictions using the
* <code>setAccessible</code> method. <code>False</code> will
* only match public fields.
* @throws IllegalArgumentException if <code>target</code> or
* <code>fieldName</code> is null
* @throws IllegalAccessException if the field is not made accessible
*/
public static void writeField(Object target, String fieldName, Object value, boolean forceAccess)
throws IllegalAccessException {
if (target == null) {
throw new IllegalArgumentException("target object must not be null");
}
Class cls = target.getClass();
Field field = getField(cls, fieldName, forceAccess);
if (field == null) {
throw new IllegalArgumentException("Cannot locate declared field " + cls.getName()
+ "." + fieldName);
}
// already forced access above, don't repeat it here:
writeField(field, target, value);
}
// Useful member methods
private static class MemberUtils {
private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED
| Modifier.PRIVATE;
public static void setAccessibleWorkaround(AccessibleObject o) {
if (o == null || o.isAccessible()) {
return;
}
Member m = (Member) o;
if (Modifier.isPublic(m.getModifiers())
&& isPackageAccess(m.getDeclaringClass().getModifiers())) {
try {
o.setAccessible(true);
} catch (SecurityException e) { // NOPMD
// ignore in favor of subsequent IllegalAccessException
}
}
}
/**
* Returns whether a given set of modifiers implies package access.
*
* @param modifiers to test
* @return true unless package/protected/private modifier detected
*/
public static boolean isPackageAccess(int modifiers) {
return (modifiers & ACCESS_TEST) == 0;
}
}
}
@@ -1,106 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.reflect;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
/**
* Represents a traditional int field enum.
*
* @author Kristian
*/
public class IntEnum {
// Used to convert between IDs and names
protected BiMap<Integer, String> members = HashBiMap.create();
/**
* Registers every declared integer field.
*/
public IntEnum() {
registerAll();
}
/**
* Registers every public int field as a member.
*/
protected void registerAll() {
try {
// Register every int field
for (Field entry : this.getClass().getFields()) {
if (entry.getType().equals(int.class)) {
registerMember(entry.getInt(this), entry.getName());
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* Registers a member.
* @param id - id of member.
* @param name - name of member.
*/
protected void registerMember(int id, String name) {
members.put(id, name);
}
/**
* Determines whether or not the given member exists.
* @param id - the ID of the member to find.
* @return TRUE if a member with the given ID exists, FALSE otherwise.
*/
public boolean hasMember(int id) {
return members.containsKey(id);
}
/**
* Retrieve the ID of the member with the given name.
* @param name - name of member to retrieve.
* @return ID of the member, or NULL if not found.
*/
public Integer valueOf(String name) {
return members.inverse().get(name);
}
/**
* Retrieve the name of the member with the given id.
* @param id - id of the member to retrieve.
* @return Declared name of the member, or NULL if not found.
*/
public String getDeclaredName(Integer id) {
return members.get(id);
}
/**
* Retrieve the ID of every registered member.
* @return Enumeration of every value.
*/
public Set<Integer> values() {
return new HashSet<Integer>(members.keySet());
}
}
Loaded 100 of 495 files, more files were not shown because too many files have changed in this diff. Show more